From f9a9716ad27882f954680d436977395e8e9ce463 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 15 Aug 2026 23:31:17 -0700 Subject: [PATCH] feat(security): trust envelope for tracker text at every model-context ingress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Web page content has had a trust envelope since v1.38; tracker text did not — PR bodies, PR/issue comment bodies, and model-judged issue titles entered agent context raw. Anyone who can comment on a PR could put instructions in front of the agent. New lib/tracker-guard.ts + bin/gstack-issue-guard: every tracker-text read now emits inside a "BEGIN UNTRUSTED TRACKER CONTENT" envelope. Content is enveloped even when clean (a pattern scan is not proof of safety); injection-shaped lines get a visible [INJECTION-PATTERN] label; NFKC + zero-width normalization runs for DETECTION only (fullwidth/invisible evasion caught, content bytes never rewritten); forged END banners are zero-width-spliced so they can't close the envelope early. Fetch failure exits non-zero with NO envelope — never a fake-trusted empty one. Issue numbers are validated and gh is spawned via argv arrays. Patterns reuse lib/jsonl-store's INJECTION_PATTERNS single copy plus a separate TRACKER_EXTRA list (kept separate so decision/learning store write-rejection semantics don't change). 8 sites wired: greptile findings + replies fetches (metadata/body split — ids and paths stay machine-raw for reply POSTs), review.ts PR-body reads x2, land-and-deploy 3.5c, document-release PR/MR body (two-artifact flow: the enveloped rendering is what the agent READS, the raw tempfile is what the pipeline mutates, and a write-side banner tripwire aborts any edit that leaked envelope markup), and spec's issue-title dedupe (titles are model-judged for similarity, so they're ingress). Title-prefix rewrites and state-routing fetches are mechanical, not ingress — deliberately not enveloped. test/tracker-guard-wiring.test.ts is the CI tripwire: raw tracker-text reads outside the guard fail the suite unless carried by a reasoned SCANNER_EXEMPT entry; exemptions are liveness-checked so a moved site forces a re-audit. Co-Authored-By: Claude Fable 5 --- bin/gstack-issue-guard | 102 +++++++++++++ document-release/sections/release-body.md | 41 +++++- .../sections/release-body.md.tmpl | 41 +++++- land-and-deploy/SKILL.md | 5 +- land-and-deploy/SKILL.md.tmpl | 5 +- lib/tracker-guard.ts | 95 ++++++++++++ review/SKILL.md | 4 +- review/greptile-triage.md | 16 ++- scripts/resolvers/review.ts | 4 +- ship/sections/plan-completion.md | 2 +- spec/SKILL.md | 11 +- spec/SKILL.md.tmpl | 11 +- test/tracker-guard-wiring.test.ts | 136 ++++++++++++++++++ test/tracker-guard.test.ts | 103 +++++++++++++ 14 files changed, 554 insertions(+), 22 deletions(-) create mode 100755 bin/gstack-issue-guard create mode 100644 lib/tracker-guard.ts create mode 100644 test/tracker-guard-wiring.test.ts create mode 100644 test/tracker-guard.test.ts diff --git a/bin/gstack-issue-guard b/bin/gstack-issue-guard new file mode 100755 index 000000000..06bc5c0bd --- /dev/null +++ b/bin/gstack-issue-guard @@ -0,0 +1,102 @@ +#!/usr/bin/env bun +/** + * gstack-issue-guard — fetch tracker text and emit it inside the untrusted + * trust envelope (lib/tracker-guard.ts). The ONLY sanctioned path for reading + * PR/issue body text into an agent's context — the wiring scanner + * (test/tracker-guard-wiring.test.ts) fails CI on raw reads outside it. + * + * gstack-issue-guard issue # gh issue: title + body + comments + * gstack-issue-guard pr-body # gh: current PR body + * gstack-issue-guard pr-comments # gh: current PR issue-comments + * gstack-issue-guard --stdin [--source