From 6fb4006e2894e9050dfae0753be89c110ab24aab Mon Sep 17 00:00:00 2001 From: yashrajbasav Date: Mon, 5 Oct 2026 10:43:43 +0530 Subject: [PATCH 1/2] fix(pi): make scanner output redaction linear Signed-off-by: yashrajbasav --- extensions/skillspector.ts | 4 ++-- tests/unit/test_pi_extension.mjs | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/extensions/skillspector.ts b/extensions/skillspector.ts index bec77df11..f71ef19d9 100644 --- a/extensions/skillspector.ts +++ b/extensions/skillspector.ts @@ -41,8 +41,8 @@ function redactSecrets(value: string): string { return value .replace(/(sk-ant-[A-Za-z0-9_-]{12,})/g, "[REDACTED_ANTHROPIC_KEY]") .replace(/(sk-[A-Za-z0-9_-]{20,})/g, "[REDACTED_OPENAI_KEY]") - .replace(/([A-Za-z0-9_]*API_KEY[=:]\s*)[^\s]+/gi, "$1[REDACTED]") - .replace(/([A-Za-z0-9_]*TOKEN[=:]\s*)[^\s]+/gi, "$1[REDACTED]"); + // Start only at a word boundary; retrying at every character is quadratic. + .replace(/\b([A-Za-z0-9_]*(?:API_KEY|TOKEN)[=:]\s*)[^\s]+/gi, "$1[REDACTED]"); } function truncateText(value: string, maxChars = 12000): { text: string; truncated: boolean } { diff --git a/tests/unit/test_pi_extension.mjs b/tests/unit/test_pi_extension.mjs index 68ad607f2..725da317c 100644 --- a/tests/unit/test_pi_extension.mjs +++ b/tests/unit/test_pi_extension.mjs @@ -74,6 +74,34 @@ test("uses installed absolute executable and preserves scan arguments without ou assert.equal(ctx.calls[0].options.cwd, ctx.workspace); }); +test("redacts long scanner output without retrying every word character", { timeout: 5000 }, async (t) => { + const ctx = await setup(t, () => ({ + code: 0, + stdout: "A".repeat(1_000_000), + stderr: "B".repeat(1_000_000), + })); + const result = await ctx.scan(); + assert.equal(result.details.stdoutTruncated, true); + assert.equal(result.details.stderrTruncated, true); + assert.ok(result.content[0].text.length < 19_000); +}); + +test("redacts complete secrets before truncating at display boundaries", async (t) => { + const ctx = await setup(t, () => ({ + code: 0, + stdout: " ".repeat(11_990) + "sk-" + "x".repeat(32), + stderr: "NPM_TOKEN=synthetic-token\nCUSTOM_API_KEY: synthetic-key\napi_key=lower-key", + })); + const result = await ctx.scan(); + const text = result.content[0].text; + for (const secret of ["sk-", "synthetic-token", "synthetic-key", "lower-key"]) { + assert.equal(text.includes(secret), false); + } + assert.match(text, /NPM_TOKEN=\[REDACTED\]/); + assert.match(text, /CUSTOM_API_KEY: \[REDACTED\]/); + assert.match(text, /api_key=\[REDACTED\]/); +}); + test("finds the Windows virtualenv executable without a PATH fallback", async (t) => { const ctx = await setup(t); rmSync(ctx.bin); From 31fdf49e39b80d23b78750c35e95e1253011f706 Mon Sep 17 00:00:00 2001 From: yashrajbasav Date: Mon, 5 Oct 2026 21:22:18 +0530 Subject: [PATCH 2/2] test(pi): enforce synchronous redaction runtime budget Signed-off-by: yashrajbasav --- tests/unit/test_pi_extension.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_pi_extension.mjs b/tests/unit/test_pi_extension.mjs index 725da317c..54c0f8299 100644 --- a/tests/unit/test_pi_extension.mjs +++ b/tests/unit/test_pi_extension.mjs @@ -9,6 +9,7 @@ import { copyFileSync, existsSync, linkSync, mkdirSync, mkdtempSync, readFileSyn import { registerHooks } from "node:module"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; +import { performance } from "node:perf_hooks"; import test from "node:test"; import { pathToFileURL } from "node:url"; @@ -77,10 +78,13 @@ test("uses installed absolute executable and preserves scan arguments without ou test("redacts long scanner output without retrying every word character", { timeout: 5000 }, async (t) => { const ctx = await setup(t, () => ({ code: 0, - stdout: "A".repeat(1_000_000), - stderr: "B".repeat(1_000_000), + stdout: "A".repeat(100_000), + stderr: "B".repeat(100_000), })); + const started = performance.now(); const result = await ctx.scan(); + const elapsed = performance.now() - started; + assert.ok(elapsed < 1000, `redaction took ${elapsed.toFixed(1)} ms`); assert.equal(result.details.stdoutTruncated, true); assert.equal(result.details.stderrTruncated, true); assert.ok(result.content[0].text.length < 19_000);