import test from "node:test"; import assert from "node:assert/strict"; const { sanitizeVscodeRequestBody, sanitizeVscodeRequest } = await import("../../src/app/api/v1/vscode/contextSanitizer.ts"); delete process.env.OMNIROUTE_VSCODE_SANITIZE_CONTEXT; type FileAttachment = { filePath: string; content: string; }; type TestMessage = { role: string; content: string; }; type TestPayload = { attachments?: FileAttachment[]; editorContext?: FileAttachment; messages?: TestMessage[]; }; test("vscode sanitizer preserves explicit attachments and strips implicit editor context", () => { const result = sanitizeVscodeRequestBody({ messages: [ { role: "user", content: "Review the attached Dockerfile.hermes.", }, ], attachments: [ { filePath: "/repo/Dockerfile.hermes", content: "FROM nousresearch/hermes-agent:latest", }, ], editorContext: { filePath: "/repo/hermes-bootstrap.sh", content: "#!/usr/bin/env bash\necho should-not-forward", }, }); assert.equal(result.changed, true); const sanitizedBody = result.body as TestPayload; assert.equal(sanitizedBody.attachments?.[0].filePath, "/repo/Dockerfile.hermes"); assert.equal(sanitizedBody.attachments?.[0].content.includes("nousresearch"), true); assert.equal("editorContext" in sanitizedBody, false); assert.deepEqual(result.audit.removedImplicitKeys, ["editorContext"]); }); test("vscode sanitizer redacts sensitive explicit file contents", () => { const result = sanitizeVscodeRequestBody({ attachments: [ { filePath: "/repo/local-docs/server-access.md", content: "ssh password and production host details", }, ], }); assert.equal(result.changed, true); const sanitizedBody = result.body as TestPayload; assert.equal(sanitizedBody.attachments?.[0].filePath, "/repo/local-docs/server-access.md"); assert.equal(sanitizedBody.attachments?.[0].content, "[REDACTED SENSITIVE CONTEXT]"); assert.deepEqual(result.audit.redactedSensitivePaths, ["/repo/local-docs/server-access.md"]); }); test("vscode sanitizer removes implicit editor text blocks unless they reference explicit files", () => { const result = sanitizeVscodeRequestBody({ attachments: [ { filePath: "/repo/Dockerfile.hermes", content: "FROM alpine", }, ], messages: [ { role: "user", content: "Current file:\n/repo/hermes-bootstrap.sh\nsecret bootstrap content\n\nUser request:\nReview the Dockerfile.hermes", }, ], }); assert.equal(result.changed, true); const sanitizedBody = result.body as TestPayload; assert.equal(sanitizedBody.messages?.[0].content.includes("hermes-bootstrap.sh"), false); assert.equal(sanitizedBody.messages?.[0].content.includes("User request:"), true); }); test("vscode sanitizer leaves ordinary chat payloads unchanged", () => { const payload = { model: "gpt-5.5-medium", messages: [{ role: "user", content: "Explain Dockerfile best practices." }], stream: true, }; const result = sanitizeVscodeRequestBody(payload); assert.equal(result.changed, false); assert.equal(result.body, payload); assert.deepEqual(result.audit.removedImplicitKeys, []); assert.deepEqual(result.audit.redactedSensitivePaths, []); }); test("vscode request sanitizer rewrites JSON body and keeps auth headers", async () => { const request = new Request("http://localhost/api/v1/vscode/token/chat/completions", { method: "POST", headers: { "content-type": "application/json", "x-api-key": "test-key", }, body: JSON.stringify({ messages: [{ role: "user", content: "Analyze attachment" }], editorContext: { filePath: "/repo/open-file.ts", content: "not explicit" }, }), }); const sanitizedRequest = await sanitizeVscodeRequest(request); const body = (await sanitizedRequest.json()) as TestPayload; assert.equal(sanitizedRequest.headers.get("x-api-key"), "test-key"); assert.equal("editorContext" in body, false); }); test("vscode sanitizer preserves ordinary prose mentioning credentials/secrets/kubeconfig", () => { // Regression: the sensitive-path keyword patterns (credentials/secrets/kubeconfig) // must NOT redact free-form chat text. They only flag sensitive *file paths* at the // object level — applying them to prose corrupted legitimate coding prompts. const prose = "How do I store API credentials and secrets in production, and edit the kubeconfig?"; const payload = { messages: [{ role: "user", content: prose }], }; const result = sanitizeVscodeRequestBody(payload); assert.equal(result.changed, false); const sanitizedBody = result.body as TestPayload; assert.equal(sanitizedBody.messages?.[0].content, prose); assert.equal(sanitizedBody.messages?.[0].content.includes("[REDACTED"), false); assert.deepEqual(result.audit.redactedSensitivePaths, []); }); test("vscode sanitizer still redacts sensitive file paths referenced in object content", () => { // The object-level path+content redaction must keep working after the text-scan // narrowing — a structured attachment whose path matches a keyword pattern is redacted. const result = sanitizeVscodeRequestBody({ attachments: [{ filePath: "/home/user/.aws/credentials", content: "aws_secret_access_key=..." }], }); assert.equal(result.changed, true); const sanitizedBody = result.body as TestPayload; assert.equal(sanitizedBody.attachments?.[0].content, "[REDACTED SENSITIVE CONTEXT]"); assert.deepEqual(result.audit.redactedSensitivePaths, ["/home/user/.aws/credentials"]); });