From c85f32b4794bceacb813c9cec165fc7892db69dd Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Wed, 5 Aug 2026 16:16:49 +0000 Subject: [PATCH] Validate knowledge-base documents during dry-run scans Reuse full scan document validation during SDK preflight so dry-run scans reject missing, unsupported, malformed, empty, and symlinked knowledge-base inputs before runtime initialization. --- sdk/typescript/src/api.ts | 7 +++++++ sdk/typescript/tests-ts/api.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index 8cabdbdf..ae6b0811 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -322,6 +322,13 @@ export class CodexSecurity { await realpath(tmpdir()), "temporary", ); + if (options.knowledgeBasePaths?.length) { + const knowledgeBase = await prepareKnowledgeBase( + options.knowledgeBasePaths, + options.signal, + ); + await knowledgeBase.cleanup(); + } const configuration = await mergedCodexConfig(this.config); const model = scanModelConfiguration(configuration); validateScanCostLimit(options.maxCostUsd, model.model); diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index 996c6a2b..4ee5fbe4 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -847,9 +847,13 @@ describe("CodexSecurity orchestration", () => { const repository = join(root, "repository"); const knowledgeBase = join(root, "threat-model.md"); const invalidDocument = join(root, "broken.pdf"); + const unsupportedDocument = join(root, "unsupported.exe"); + const emptyDirectory = join(root, "empty"); await mkdir(repository); + await mkdir(emptyDirectory); await writeFile(knowledgeBase, "# Threat model\nPublic API is in scope.\n"); await writeFile(invalidDocument, "not a PDF"); + await writeFile(unsupportedDocument, "not a supported document"); let runtimeStarted = false; const client = new TestClient( {}, @@ -865,6 +869,28 @@ describe("CodexSecurity orchestration", () => { await expect( client.preflight(repository, { knowledgeBasePaths: [knowledgeBase] }), ).resolves.toMatchObject({ knowledgeBasePaths: [knowledgeBase] }); + const invalidDocuments: Array<[string, string]> = [ + [join(root, "missing.md"), "ENOENT"], + [unsupportedDocument, "Unsupported knowledge base document"], + [invalidDocument, "Cannot extract text from knowledge base PDF"], + [ + emptyDirectory, + "Knowledge base directory contains no supported documents", + ], + ]; + if (process.platform !== "win32") { + const linkedDocument = join(root, "linked.md"); + await symlink(knowledgeBase, linkedDocument); + invalidDocuments.push([ + linkedDocument, + "Knowledge base paths cannot be symbolic links", + ]); + } + for (const [path, message] of invalidDocuments) { + await expect( + client.preflight(repository, { knowledgeBasePaths: [path] }), + ).rejects.toThrow(message); + } await expect( client.run(repository, { knowledgeBasePaths: [join(root, "missing.md")],