From a74bfc8cc8071630e6e13c8402758ce8d03ab2e3 Mon Sep 17 00:00:00 2001 From: Dipesh Babu Date: Thu, 6 Aug 2026 18:41:28 -0400 Subject: [PATCH] Pass registered scan ID to agent prompt --- sdk/typescript/src/api.ts | 22 +++++---- sdk/typescript/tests-ts/api.test.ts | 73 ++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index 3e94f2dd..aa832458 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -633,14 +633,6 @@ export class CodexSecurity { `Shell-visible plugin root must be outside CODEX_HOME: ${canonicalShellPluginRoot}`, ); } - const basePrompt = await scanPrompt( - shellPluginRoot, - normalized, - mode, - runtime.configPath !== undefined, - knowledgeBase !== null, - ); - checkOpen(); const expectation: ScanExpectation = { repository: repo, repositoryRevision: await ( @@ -814,6 +806,15 @@ export class CodexSecurity { } activeScan = { id: scanId, options: workbenchOptions }; checkOpen(); + const basePrompt = await scanPrompt( + shellPluginRoot, + normalized, + mode, + scanId, + runtime.configPath !== undefined, + knowledgeBase !== null, + ); + checkOpen(); const feedback = await workbench( { ...workbenchOptions, @@ -1953,6 +1954,7 @@ async function scanPrompt( pluginRoot: string, target: NormalizedTarget, mode: ScanMode, + scanId: string, hasConfigPath = false, hasKnowledgeBase = false, ): Promise { @@ -1969,7 +1971,7 @@ async function scanPrompt( "Run this Codex Security scan non-interactively.", ...(mode === "deep" ? [ - 'The SDK has already registered this scan. Call start_codex_security_deep_scan with { scanId: "$CODEX_SECURITY_SCAN_ID" }; never pass targetPath or create another scan.', + `The SDK has already registered this scan. Call start_codex_security_deep_scan with { scanId: ${JSON.stringify(scanId)} }; never pass targetPath or create another scan.`, ] : []), ...(skillName === "deep-security-scan" @@ -1981,7 +1983,7 @@ async function scanPrompt( 'Use "$PYTHON" as for every plugin helper; replace any literal python or python3 helper invocation with this exact interpreter.', 'Repository root: "$CODEX_SECURITY_REPOSITORY"', 'Use this exact scan directory for all scan output: "$CODEX_SECURITY_SCAN_DIR"', - 'Use exactly "$CODEX_SECURITY_SCAN_ID" as the scan ID in the manifest, findings, and coverage.', + `Use exactly ${JSON.stringify(scanId)} as the scan ID in the manifest, findings, and coverage.`, 'Use exactly "$CODEX_SECURITY_TARGET_ID" as scan.target.targetId; do not derive a different target ID.', 'Use exactly "$CODEX_SECURITY_TARGET_DISPLAY_NAME" as scan.target.displayName; do not infer a display name from the Git remote.', 'Use exactly "$CODEX_SECURITY_TARGET_KIND" as scan.target.kind; do not infer the target kind from the checkout.', diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index c8045b47..c65455e2 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -2559,6 +2559,77 @@ describe("CodexSecurity orchestration", () => { await client.close(); }); + test("uses the registered scan ID in standard and deep prompts", async () => { + const scanId = "123e4567-e89b-12d3-a456-426614174000"; + + for (const mode of ["standard", "deep"] as const) { + for (const withFeedback of [false, true]) { + const root = await temporaryDirectory(); + const repository = join(root, "repository"); + const codexHome = join(root, "codex-home"); + const scanDir = join(root, "scan"); + await mkdir(repository); + await mkdir(codexHome); + await mkdir(scanDir, { mode: 0o700 }); + let prompt = ""; + const client = new TestClient( + {}, + { + environment: {}, + prepareRuntime: async () => preparedRuntime(codexHome), + resolvePluginPython: async () => "/managed/python", + prepareOutputDir: async () => scanDir, + repositoryRevision: async () => "deadbeef", + runWorkbench: async ( + _options: unknown, + args: readonly string[], + ) => { + if (args[0] === "register-cli-scan") { + return { ...mockScanRegistration(args), scanId }; + } + if (args[0] === "get-scan-feedback") { + return { + scanId, + targetId: "target_sha256_example", + falsePositives: withFeedback + ? [{ reason: "The finding is no longer reproducible." }] + : [], + }; + } + return {}; + }, + createCodex: () => ({ + startThread: () => ({ + id: null, + async runStreamed(input: string) { + prompt = input; + throw new Error("prompt captured"); + }, + }), + }), + }, + ); + + await expect(client.run(repository, { mode })).rejects.toThrow( + "prompt captured", + ); + expect(prompt).toContain( + `Use exactly "${scanId}" as the scan ID in the manifest, findings, and coverage.`, + ); + expect(prompt).not.toContain("$CODEX_SECURITY_SCAN_ID"); + if (mode === "deep") { + expect(prompt).toContain( + `start_codex_security_deep_scan with { scanId: "${scanId}" }`, + ); + } + if (withFeedback) { + expect(prompt).toContain("false_positive_feedback.json"); + } + await client.close(); + } + } + }); + test("rejects feedback from another scan or invalid reviewer feedback", async () => { const scanId = "scan_example_001"; const targetId = "target_sha256_example"; @@ -3986,7 +4057,7 @@ describe("CodexSecurity orchestration", () => { ); expect(prompt).toContain("$codex-security:deep-security-scan"); expect(prompt).toContain( - 'start_codex_security_deep_scan with { scanId: "$CODEX_SECURITY_SCAN_ID" }', + 'start_codex_security_deep_scan with { scanId: "scan_example_001" }', ); expect(prompt).not.toContain( "This exhaustive scan authorizes the delegated-worker phases",