Repository navigation
Description
Outcome
Session taint (G6) is a rare, meaningful state that marks a session which actually took in untrusted content. When taint restricts something, the user can see why and can decide. Ordinary governed chats are never tainted by their own commands or by the host's own capabilities. When a tainted session reaches a new destination, the user is asked rather than met with a silent refusal.
Why now
On a hosted 0.44.0 install, taint fired in effectively every governed session:
- For two days, every session was tainted within its first second by the Claude runtime's own traffic (ui-server: the Claude runtime's own api.anthropic.com traffic taints every governed turn, blocking WebFetch and reranking #3244, workaround in place, fix awaiting release).
- With that fixed, a session is still tainted by its first ordinary shell command:
!brain diagram --helprecordedsource: "unclassified_command". - Once tainted, every new host is refused with
taint_allowlist. That includes WebFetch,ghandcurl, and the host's own metered provider relay for image generation, embeddings and completions (ui-server: a relayed provider request taints the session and is narrowed after taint, so tool order decides whether WebFetch or reranking works #3246). - The agent sees
proxy refused the connection, or a provider error with an unrelated hint, and tells the user that the host is broken.
A signal that fires on every session cannot tell a poisoned session from a clean one. A restriction that the user can neither see nor override reads as an outage. The current instance workaround is a hostname-wide taint.egress.allow, which is the opposite of what G6 intends.
Shared context
- The rule: "Untrusted input (G6)",
docs/decisions/governed-writable-workers.md:3760, including its "Implemented — 2026-10-22 (ui-server: a session that ingested untrusted content gets tighter egress and outward rules and flags its steering writes #1667)" note. The rule text says the Bash classifier taints "before the egress proxy lands". The implemented note keeps it unconditionally. - Where taint is recorded:
- Input sources:
inputTaintSource,packages/ui-server/src/activity/ingestion-taint.ts:82-92, called frompackages/ui-server/src/activity/recorder.ts:336. - Shell classifier:
commandTaintSource,packages/ui-server/src/activity/command-taint.ts:2-27. It exempts only barelsandbrain search. - Proxy connection taint:
admitted,packages/ui-server/src/brain/turn-egress.ts:133-143.
- Input sources:
- Narrowing:
narrowed,packages/ui-sdk/src/server/egress-proxy.ts:119-120, refuses outright. Outward hosts already have an approval path,approveOutward(packages/ui-server/src/brain/turn-egress.ts:150-158), which narrowing never reaches. - Provider relay ruling: ui-server: a relayed provider request taints the session and is narrowed after taint, so tool order decides whether WebFetch or reranking works #3246 (option C) exempts validated host-relayed provider requests from taint and from narrowing.
Constraints and non-goals
- G6's purpose stays: content the user did not author must not silently steer data to new destinations or outward actions.
- No unsandboxed fallback, and no global "taint off" switch as the product answer.
- Taint remains sticky for a session.
- Provider history stays append-only. Any new card or notice is new tail content, never an edit to an earlier message.
- Autonomous turns gain nothing; they keep refusing rather than asking.
- Policy schema changes are additive.
Breakdown
- ui-server: a relayed provider request taints the session and is narrowed after taint, so tool order decides whether WebFetch or reranking works #3246 (a sibling under Epic: controlled egress, credentials and outward actions #1645, not a native child here): host-relayed provider requests neither taint nor are narrowed (ruled C).
- ui-server: an ordinary shell command taints a governed session even when the egress proxy records real connections #3429: shell commands do not taint a session that runs on the egress proxy; only real untrusted input and real non-relay connections do (needs ruling).
- ui-sdk: a tainted session's new destination is refused silently instead of asking the user #3430: a tainted session's new destination asks the user instead of being refused (needs ruling).
- ui-sdk, modules: taint refusals reach the agent as unrelated errors such as "region not supported" #3431: taint refusals say why, and provider errors stop adding unrelated hints.
- Spike: classify a tainted session's outgoing calls for likely data extraction, with heuristics first and JEV for the ambiguous band #3434 (spike, blocked by ui-sdk: a tainted session's new destination is refused silently instead of asking the user #3430): classify a tainted session's outgoing calls for likely extraction, so that only suspicious calls ask.
Definition of done
- On a fixture governed session on the egress proxy, a turn that runs ordinary shell commands and relayed provider calls, and reads no untrusted content, records no
taint. - A session that does ingest untrusted content still records it, and a new destination then asks or is refused per the ruling, with the reason visible to both the user and the agent.
- The decision record states the revised trigger and narrowing semantics, with a dated supersession.
- A host no longer needs a hostname-wide
taint.egress.allowentry: with it removed, ordinary governed sessions keep every capability listed above.
Triage 2026-11-03 (main 3d3516a02): added type: feat (the epic changes taint and refusal behaviour). Marked #3246 as a sibling rather than a child, and made the last definition-of-done bullet checkable without reference to a particular installation.
Activity