From 1aa9046981892434d62a0e9da6a229815167d401 Mon Sep 17 00:00:00 2001 From: garrytan Date: Tue, 3 Nov 2026 19:03:46 +0000 Subject: [PATCH] review-risk: sealed custody, redaction and outside-model egress code class as security (GST-1177) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- lib/review-risk.ts | 4 +++- test/review-depth-lean.test.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/review-risk.ts b/lib/review-risk.ts index 522f6fcb3..5535a4243 100644 --- a/lib/review-risk.ts +++ b/lib/review-risk.ts @@ -51,7 +51,8 @@ export const SECRET_LINE = [ /\b(?:keychain|keytar|find-generic-password|find-internet-password|secret-tool|git-credential|credential\.helper)\b/i, /\bgh auth token\b/, /(?:^|[\s'"/])\.netrc\b/, ]; -const SECURITY_PATH_TOKENS = ['trust', 'redact', 'redaction', 'redactor', 'sanitize', 'sanitizer', 'neutralize', 'neutralizer', 'consent', 'escape']; +/** Sealed custody (harness-evals#237: scripts/sealed/seal.ts) and egress code are trust boundaries too (GST-1177). */ +const SECURITY_PATH_TOKENS = ['trust', 'redact', 'redaction', 'redactor', 'sanitize', 'sanitizer', 'neutralize', 'neutralizer', 'consent', 'escape', 'seal', 'sealed', 'custody', 'egress']; /** Coarse, linear-time rules (GST-850 B+): any changed line naming the child_process module, any outbound HTTP client call; quantifiers bounded. */ export const SECURITY_LINE = [ /\bshell\s*:\s*true\b/, /\bshell\s*=\s*True\b/, /(? { + const sec = (p: string, lines: string[] = ['export const x = 1;']) => matchRisk({ codePaths: [p], added: { [p]: lines }, scopeAuth: false }).has('security'); + expect(sec('scripts/sealed/seal.ts')).toBe(true); + expect(sec('lib/eval-custody.ts')).toBe(true); + expect(sec('lib/autoplan-lean.ts', ["import { redactFindingSpans } from './redact-engine';"])).toBe(true); + expect(sec('lib/autoplan-lean.ts', [' const redacted = (patch: string) => redactFindingSpans(patch) ?? "";'])).toBe(true); + expect(sec('lib/autoplan-lean.ts', ['const SECRET_NAME = /(^|\\/)(\\.env[^/]*)$/i;'])).toBe(true); + expect(sec('lib/review-x.ts', ["import { runOutsideVoice } from './outside-voice-runner';"])).toBe(true); + expect(sec('lib/autoplan-lean.ts', ['const INLINE_DRAFT_BYTES = 64_000;'])).toBe(false); + expect(sec('lib/seasonal.ts')).toBe(false); + expect(classifyDiff({ paths: ['scripts/sealed/seal.ts'], gateTagged: 0 }).risk).toContain('security'); + }); test('a sqlite file is data even when a script names it', () => { const c = classifyDiff({ paths: [sqlite, 'scripts/load.py'], gateTagged: 0, perPath: [{ path: sqlite, lines: 0, binary: true }, { path: 'scripts/load.py', lines: 4, binary: false }], consumed: [sqlite] }); expect([c.class, c.errors.map(e => e.code), c.runtimeLines]).toEqual(['code', [], 4]);