/** * Thin wrapper around the @openai/codex-security SDK. * The SDK is imported lazily so extension startup stays fast and a missing * install produces a helpful error instead of a load failure. */ import type { FindingLike } from "./format.ts"; export interface ScanParams { /** Repository (or directory) to scan. */ path: string; mode?: "standard" | "deep"; auth?: "auto" | "chatgpt" | "api-key"; /** Scan only changes relative to this base ref (working tree diff). */ diffBase?: string; /** When set with diffBase, scan the ref range base...head. */ diffHead?: string; maxCostUsd?: number; outputDir?: string; knowledgeBasePaths?: string[]; } export interface ScanOutcome { findings: FindingLike[]; reportPath: string; findingsPath: string; coveragePath: string; sarifPath: string | null; scanDir: string; cost: { model?: string; estimatedUsd?: number; inputTokens?: number; outputTokens?: number; } | null; } export interface AuthStatus { authenticated: boolean; details: string; envHints: string[]; } type CodexSecuritySdk = typeof import("@openai/codex-security"); async function loadSdk(): Promise { try { return await import("@openai/codex-security"); } catch (error) { throw new Error( `Failed to load @openai/codex-security: ${error instanceof Error ? error.message : String(error)}\n` + "Reinstall the pi-codex-security package (pi install npm:pi-codex-security) and ensure Node.js >= 22 and Python >= 3.10 are available.", ); } } const PHASE_LABELS: Record = { ranking: "ranking files for review", file_review: "reviewing files", validation: "validating findings", attack_path: "analyzing attack paths", }; export async function runCodexSecurityScan( params: ScanParams, onProgress: (message: string) => void, signal?: AbortSignal, ): Promise { const sdk = await loadSdk(); const security = new sdk.CodexSecurity(); try { const target = params.diffBase !== undefined ? params.diffHead !== undefined ? sdk.DiffTarget.refs({ base: params.diffBase, head: params.diffHead, }) : sdk.DiffTarget.workingTree({ base: params.diffBase }) : "repository"; onProgress("Starting Codex Security scan…"); const result = await security.run(params.path, { target, ...(params.mode !== undefined ? { mode: params.mode } : {}), ...(params.auth !== undefined ? { auth: params.auth } : {}), ...(params.maxCostUsd !== undefined ? { maxCostUsd: params.maxCostUsd } : {}), ...(params.outputDir !== undefined ? { outputDir: params.outputDir } : {}), ...(params.knowledgeBasePaths !== undefined ? { knowledgeBasePaths: params.knowledgeBasePaths } : {}), ...(signal !== undefined ? { signal } : {}), onScanStarted: () => onProgress("Scan started — inventorying repository…"), onWorkerStatus: (status) => { if (status.kind === "preflight") { onProgress(`Preflight checks (delegated workers: ${status.delegation})…`); } else { const label = PHASE_LABELS[status.phase] ?? status.phase; onProgress( `Workers ${label}: ${status.started}/${status.planned} started…`, ); } }, onReconnect: (attempt, maxAttempts, details) => { onProgress( `Reconnecting (${attempt}/${maxAttempts})${details?.reason ? ` — ${details.reason}` : ""}…`, ); }, onCost: (cost) => { onProgress(`Scanning… estimated cost so far: $${cost.estimatedUsd.toFixed(4)}`); }, }); return { findings: result.findings.findings as FindingLike[], reportPath: result.reportPath, findingsPath: result.findingsPath, coveragePath: result.coveragePath, sarifPath: result.sarifPath, scanDir: result.scanDir, cost: result.cost ? { model: result.cost.model, estimatedUsd: result.cost.estimatedUsd, inputTokens: result.cost.inputTokens, outputTokens: result.cost.outputTokens, } : null, }; } finally { // Never let a close() failure mask the scan result or error. await security.close().catch(() => {}); } } export async function checkAuthStatus(): Promise { const sdk = await loadSdk(); const security = new sdk.CodexSecurity(); try { const account = await security.account(); const envHints: string[] = []; if (process.env.OPENAI_API_KEY) envHints.push("OPENAI_API_KEY is set"); if (process.env.CODEX_API_KEY) envHints.push("CODEX_API_KEY is set"); if (!account.authenticated && envHints.length === 0) { envHints.push( "No API key env vars found — run `npx codex-security login` or set OPENAI_API_KEY", ); } return { authenticated: account.authenticated, details: account.details, envHints, }; } finally { await security.close().catch(() => {}); } }