"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.checkoutRepo = void 0; const utils_1 = require("./utils"); const fs_1 = require("fs"); const context_1 = require("./context"); const path = __importStar(require("path")); const os_1 = require("os"); const url_config_1 = require("./url-config"); /** * Retry a function with exponential backoff */ const retryWithBackoff = async (fn, maxRetries = 3, baseDelay = 1000) => { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { if (attempt === maxRetries) { throw error; } const delay = baseDelay * Math.pow(2, attempt - 1); console.log(`Git operation failed (attempt ${attempt}/${maxRetries}), retrying in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); } } throw new Error('Max retries exceeded'); }; /** * Clones the repository into a temporary directory for analysis. * Required because some auditing logic uses 'git grep' or 'git commit'. */ const checkoutRepo = async (tempDir) => { const ctx = (0, context_1.getCurrentContext)(); if (!ctx) throw new Error('No context available for checkout'); const { owner, repo } = ctx.repo; const installationId = ctx.probotContext.payload.installation?.id; if (!installationId) { throw new Error('Installation ID missing from payload. Ensure the app is correctly installed on the repository.'); } // We need an installation token for cloning // Probot context provides this through 'context.octokit' if needed, // but it's easier to use the authenticated URL if possible. // Standard App cloning URL: https://x-access-token:@github.com/owner/repo.git const tokenResp = await ctx.probotContext.octokit.apps.createInstallationAccessToken({ installation_id: installationId }); const token = tokenResp.data.token; const headBranch = ctx.probotContext.payload.pull_request.head.ref; const headSha = ctx.probotContext.payload.pull_request.head.sha; if ((0, fs_1.existsSync)(tempDir)) { (0, fs_1.rmSync)(tempDir, { recursive: true, force: true }); } (0, fs_1.mkdirSync)(tempDir, { recursive: true }); // SECURITY: Use GIT_ASKPASS instead of embedding token in clone URL // Write askpass outside tempDir so git clone '.' sees an empty target const askpassPath = path.join((0, os_1.tmpdir)(), '.git-askpass-' + Date.now()); const isWin = process.platform === 'win32'; (0, fs_1.writeFileSync)(askpassPath, isWin ? `@echo off\r\necho ${token}\r\n` : `#!/bin/sh\necho "${token}"\n`, isWin ? {} : { mode: 0o700 }); const cloneUrl = `https://github.com/${owner}/${repo}.git`; const askpassEnv = { GIT_ASKPASS: askpassPath }; console.log(`Cloning ${owner}/${repo} (shallow)...`); // SAFETY: Use prixSpawn with argument array to prevent shell injection // PERFORMANCE: --depth=1 for shallow clone (much faster) const cloneResult = await retryWithBackoff(async () => { // Clean and recreate temp dir before each attempt to avoid partial-checkout conflicts if ((0, fs_1.existsSync)(tempDir)) { (0, fs_1.rmSync)(tempDir, { recursive: true, force: true }); } (0, fs_1.mkdirSync)(tempDir, { recursive: true }); (0, fs_1.writeFileSync)(askpassPath, isWin ? `@echo off\r\necho ${token}\r\n` : `#!/bin/sh\necho "${token}"\n`, isWin ? {} : { mode: 0o700 }); const result = (0, utils_1.prixSpawn)('git', ['clone', '--quiet', '--depth=1', '--no-checkout', cloneUrl, '.'], { cwd: tempDir, env: askpassEnv }); if (result.status !== 0) { throw new Error(`Git clone failed: ${result.stderr}`); } return result; }, 3, 2000); // 3 retries, 2s base delay // Fetch specific commit we need const fetchResult = await retryWithBackoff(async () => { const result = (0, utils_1.prixSpawn)('git', ['fetch', '--depth=1', 'origin', headSha], { cwd: tempDir, env: askpassEnv }); if (result.status !== 0) { throw new Error(`Git fetch failed: ${result.stderr}`); } return result; }, 2, 1000); // 2 retries, 1s base delay const checkoutResult = (0, utils_1.prixSpawn)('git', ['checkout', headSha], { cwd: tempDir }); if (checkoutResult.status !== 0) { throw new Error(`Git checkout failed: ${checkoutResult.stderr}`); } // Clean up askpass script try { (0, fs_1.rmSync)(askpassPath, { force: true }); } catch { } // Configure git user for Remedy PRs (0, utils_1.prixSpawn)('git', ['config', 'user.name', url_config_1.BRANDING.BOT_NAME], { cwd: tempDir }); (0, utils_1.prixSpawn)('git', ['config', 'user.email', url_config_1.BRANDING.BOT_EMAIL], { cwd: tempDir }); return tempDir; }; exports.checkoutRepo = checkoutRepo; //# sourceMappingURL=checkout.js.map