From ff24e8769271fd19d31e15aee08242a5cf229b28 Mon Sep 17 00:00:00 2001 From: "xyanua." Date: Wed, 4 Nov 2026 09:40:56 +0700 Subject: [PATCH 1/2] Omit snapshots with quoted JSON credentials --- lib/distill/prompt.js | 6 ++++-- lib/redact.js | 5 ++++- test/distill-prompt.test.js | 12 ++++++++++++ test/runtime/project.test.js | 9 +++++++-- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/lib/distill/prompt.js b/lib/distill/prompt.js index 69cef88..e80424b 100644 --- a/lib/distill/prompt.js +++ b/lib/distill/prompt.js @@ -5,7 +5,7 @@ import { checkExitOf, verdictOf } from '../checks.js'; import { withoutSecrets } from '../redact.js'; import { checkSourceOf } from '../reuse.js'; import { TRIGGERS } from './candidate.js'; -import { DIFF_OMITTED } from './project-diff.js'; +import { DIFF_OMITTED, diffFieldsOf } from './project-diff.js'; const LIST_FIELDS = ['strategy', 'validation', 'signals', 'preconditions']; const ARGUMENT_FIELDS = ['title', 'summary', ...LIST_FIELDS]; @@ -94,6 +94,7 @@ function withinBodyLimit(body) { } export function distillBody({ platform, sessionId, candidate, args, usage, trigger = TRIGGERS.SESSION_END }) { + const { diff, ...project } = candidate.project ?? {}; return withinBodyLimit({ platform, trigger, @@ -103,7 +104,8 @@ export function distillBody({ platform, sessionId, candidate, args, usage, trigg final_answer: candidate.answer, ...args, execution: { trace: traceOf(candidate) }, - ...(candidate.project && { project: candidate.project }), + // A candidate can hold a snapshot written before the current secret filter. + ...(candidate.project && { project: { ...project, ...(typeof diff === 'string' && diffFieldsOf(diff)) } }), persist: true, publish: false, ...(usage ? { distill_usage: usage } : {}), diff --git a/lib/redact.js b/lib/redact.js index dd60c8d..3465864 100644 --- a/lib/redact.js +++ b/lib/redact.js @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 EvoMap +const JSON_SECRET = /("(?:api[_-]?key|secret|token|passw(?:or)?d|pwd|access[_-]?key|private[_-]?key|client[_-]?secret|[a-z][a-z0-9_]*_(?:key|token|secret|password|passwd|pwd|credentials?))"\s*:\s*")(?:\\[^\r\n]|[^"\\\r\n])+(")/gi; + const SECRET_PATTERNS = [ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, /\b(?:sk|pk|rk)-(?:ant-|proj-|live-|test-)?[A-Za-z0-9_-]{16,}/g, @@ -24,7 +26,8 @@ export const REDACTED = '[redacted]'; export function withoutSecrets(text) { if (typeof text !== 'string') return ''; - return SECRET_PATTERNS.reduce((cleaned, pattern) => cleaned.replace(pattern, REDACTED), text); + const redactedJson = text.replace(JSON_SECRET, '$1' + REDACTED + '$2'); + return SECRET_PATTERNS.reduce((cleaned, pattern) => cleaned.replace(pattern, REDACTED), redactedJson); } export function containsSecret(text) { diff --git a/test/distill-prompt.test.js b/test/distill-prompt.test.js index a097641..3eff88f 100644 --- a/test/distill-prompt.test.js +++ b/test/distill-prompt.test.js @@ -13,3 +13,15 @@ test('an earlier unknown check stays unknown in the model record and supplies no const body = distillBody({ platform: 'codex', sessionId: 's1', candidate, args: {} }); assert.deepEqual(body.execution.trace, [{ command: 'npm test', exit: 0, source: 'reported' }]); }); + +test('the Proxy body omits a stored snapshot with a quoted JSON credential', () => { + const project = { + root: '/project', head: 'abc123', + diff: 'diff --git a/settings.json b/settings.json\n+{"token":"opaque-example-123456789"}\n', + }; + const candidate = { checks: [], project }; + const body = distillBody({ platform: 'codex', sessionId: 's1', candidate, args: {} }); + assert.deepEqual(body.project, { root: project.root, head: project.head, diff_omitted: 'secret' }); + assert.doesNotMatch(JSON.stringify(body), /opaque-example/); + assert.match(candidate.project.diff, /opaque-example/); +}); diff --git a/test/runtime/project.test.js b/test/runtime/project.test.js index 5299eb0..facbfdc 100644 --- a/test/runtime/project.test.js +++ b/test/runtime/project.test.js @@ -48,12 +48,17 @@ test('a work tree snapshot is its root, HEAD and a diff that rebuilds it on HEAD }); test('a work tree whose diff holds a secret, or touches a file keys are kept in, sends no diff at all', async (t) => { - for (const [file, text] of [['credentials.txt', 'harmless\n'], ['a.js', 'PRIVATE_CREDENTIAL=unique-secret-123\n']]) { + for (const [file, text] of [ + ['credentials.txt', 'harmless\n'], + ['a.js', 'PRIVATE_CREDENTIAL=unique-secret-123\n'], + ['settings.json', '{"token":"opaque-example-123456789"}\n'], + ['deploy.json', '{"DEPLOY_TOKEN":"opaque-example-123456789"}\n'], + ]) { const dir = repo(t); writeFileSync(join(dir, file), text); const snapshot = await projectSnapshotOf().snapshot(dir); assert.deepEqual([snapshot.diff, snapshot.diff_omitted], [undefined, 'secret'], file); - assert.doesNotMatch(JSON.stringify(snapshot), /unique-secret-123|harmless/); + assert.doesNotMatch(JSON.stringify(snapshot), /unique-secret-123|harmless|opaque-example/); } }); From a82ace764c7e019842c2ed483bf511e207dfce4b Mon Sep 17 00:00:00 2001 From: "xyanua." Date: Wed, 4 Nov 2026 10:56:05 +0700 Subject: [PATCH 2/2] Omit snapshots with camelCase JSON credentials --- lib/redact.js | 8 ++++++-- test/distill-prompt.test.js | 2 +- test/runtime/project.test.js | 7 +++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/redact.js b/lib/redact.js index 3465864..2167f8d 100644 --- a/lib/redact.js +++ b/lib/redact.js @@ -1,7 +1,11 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 EvoMap -const JSON_SECRET = /("(?:api[_-]?key|secret|token|passw(?:or)?d|pwd|access[_-]?key|private[_-]?key|client[_-]?secret|[a-z][a-z0-9_]*_(?:key|token|secret|password|passwd|pwd|credentials?))"\s*:\s*")(?:\\[^\r\n]|[^"\\\r\n])+(")/gi; +const JSON_SECRET_PATTERNS = [ + /("(?:api[_-]?key|secret|token|passw(?:or)?d|pwd|access[_-]?key|private[_-]?key|client[_-]?secret|[a-z][a-z0-9_]*_(?:key|token|secret|password|passwd|pwd|credentials?))"\s*:\s*")(?:\\[^\r\n]|[^"\\\r\n])+(")/gi, + // Match camelCase suffixes without treating words like "monkey" as credentials. + /("[A-Za-z][A-Za-z0-9_-]*(?:Key|Token|Secret|Password|Passwd|Pwd|Credentials?)"\s*:\s*")(?:\\[^\r\n]|[^"\\\r\n])+(")/g, +]; const SECRET_PATTERNS = [ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, @@ -26,7 +30,7 @@ export const REDACTED = '[redacted]'; export function withoutSecrets(text) { if (typeof text !== 'string') return ''; - const redactedJson = text.replace(JSON_SECRET, '$1' + REDACTED + '$2'); + const redactedJson = JSON_SECRET_PATTERNS.reduce((cleaned, pattern) => cleaned.replace(pattern, '$1' + REDACTED + '$2'), text); return SECRET_PATTERNS.reduce((cleaned, pattern) => cleaned.replace(pattern, REDACTED), redactedJson); } diff --git a/test/distill-prompt.test.js b/test/distill-prompt.test.js index 3eff88f..aa4edb5 100644 --- a/test/distill-prompt.test.js +++ b/test/distill-prompt.test.js @@ -17,7 +17,7 @@ test('an earlier unknown check stays unknown in the model record and supplies no test('the Proxy body omits a stored snapshot with a quoted JSON credential', () => { const project = { root: '/project', head: 'abc123', - diff: 'diff --git a/settings.json b/settings.json\n+{"token":"opaque-example-123456789"}\n', + diff: 'diff --git a/settings.json b/settings.json\n+{"refreshToken":"opaque-example-123456789"}\n', }; const candidate = { checks: [], project }; const body = distillBody({ platform: 'codex', sessionId: 's1', candidate, args: {} }); diff --git a/test/runtime/project.test.js b/test/runtime/project.test.js index facbfdc..c034f88 100644 --- a/test/runtime/project.test.js +++ b/test/runtime/project.test.js @@ -6,6 +6,7 @@ import { join } from 'node:path'; import { test } from 'node:test'; import { DIFF_MAX_BYTES } from '../../lib/distill/project-diff.js'; +import { distillBody } from '../../lib/distill/prompt.js'; import { projectSnapshotOf } from '../../lib/runtime/project.js'; const git = (cwd, ...args) => execFileSync('git', ['-c', 'user.email=t@t', '-c', 'user.name=t', ...args], { cwd, encoding: 'utf8' }); @@ -26,6 +27,7 @@ test('a work tree snapshot is its root, HEAD and a diff that rebuilds it on HEAD writeFileSync(join(dir, 'a.js'), 'export const a = 2;\n'); mkdirSync(join(dir, 'lib')); writeFileSync(join(dir, 'lib', 'new.js'), 'export const b = 3;\n'); + writeFileSync(join(dir, 'settings.json'), '{"maxTokens":"4096","monkey":"example"}\n'); mkdirSync(join(dir, 'node_modules')); writeFileSync(join(dir, 'node_modules', 'dep.js'), 'ignored\n'); const statusBefore = git(dir, 'status', '--porcelain'); @@ -33,6 +35,8 @@ test('a work tree snapshot is its root, HEAD and a diff that rebuilds it on HEAD assert.equal(git(dir, 'status', '--porcelain'), statusBefore); assert.deepEqual([snapshot.root, snapshot.head], [git(dir, 'rev-parse', '--show-toplevel').trim(), git(dir, 'rev-parse', 'HEAD').trim()]); assert.doesNotMatch(snapshot.diff, /node_modules/); + assert.match(snapshot.diff, /"maxTokens":"4096"/); + assert.match(snapshot.diff, /"monkey":"example"/); const copy = mkdtempSync(join(tmpdir(), 'evolver-copy-')); t.after(() => rmSync(copy, { recursive: true, force: true })); @@ -53,10 +57,13 @@ test('a work tree whose diff holds a secret, or touches a file keys are kept in, ['a.js', 'PRIVATE_CREDENTIAL=unique-secret-123\n'], ['settings.json', '{"token":"opaque-example-123456789"}\n'], ['deploy.json', '{"DEPLOY_TOKEN":"opaque-example-123456789"}\n'], + ['settings.json', '{"accessToken":"opaque-example-123456789"}\n'], ]) { const dir = repo(t); writeFileSync(join(dir, file), text); const snapshot = await projectSnapshotOf().snapshot(dir); + const body = distillBody({ platform: 'codex', sessionId: 's1', candidate: { checks: [], project: snapshot }, args: {} }); + assert.doesNotMatch(JSON.stringify(body.project), /unique-secret-123|harmless|opaque-example/); assert.deepEqual([snapshot.diff, snapshot.diff_omitted], [undefined, 'secret'], file); assert.doesNotMatch(JSON.stringify(snapshot), /unique-secret-123|harmless|opaque-example/); }