Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a06eaf-3425-7f92-a0c2-bb4aa4faebe4
94 lines
4.1 KiB
Markdown
94 lines
4.1 KiB
Markdown
---
|
|
id: ops-warden-adr-0004
|
|
type: adr
|
|
title: "ADR-0004 — High-risk lanes refuse raw value streaming to agent sessions"
|
|
domain: infotech
|
|
repo: ops-warden
|
|
status: accepted
|
|
version: "1.1"
|
|
revision: "2"
|
|
owner: ops-warden
|
|
binds: "ops-warden; any agent runtime calling warden access"
|
|
created: "2026-07-20"
|
|
updated: "2026-09-04"
|
|
last_reviewed: "2026-09-04"
|
|
review_interval: 6m
|
|
enforced_by: "src/warden/access.py + src/warden/cli.py (exit 7); OpenBao policy agent-high-risk-boundary"
|
|
supersedes: ""
|
|
successor: ""
|
|
---
|
|
|
|
# ADR-0004 — High-risk lanes refuse raw value streaming to agent sessions
|
|
|
|
## Status
|
|
|
|
Accepted. Decided during WARDEN-WP-0026 (credential disclosure hygiene), in
|
|
response to a real disclosure on 2026-07-16.
|
|
|
|
## Context
|
|
|
|
On 2026-07-16 a secret value reached a captured stdout. The mechanism was ordinary:
|
|
`bao kv get -field=X` in an agent session. Nothing was misconfigured and nobody
|
|
misused a tool. The value was read correctly, by an authorized caller, using the
|
|
documented command — and an agent session records its stdout, so the value landed in
|
|
a transcript that outlives the shell.
|
|
|
|
This is a structural mismatch, not a mistake to train away. Agent sessions are
|
|
logged by design; that is what makes them reviewable. A human at a terminal sees a
|
|
value and it scrolls away. An agent "seeing" a value writes it into a durable
|
|
context that may be stored, replayed, or sent to an inference provider.
|
|
|
|
Guidance alone will not fix it. The command is correct, it is in every runbook, and
|
|
the next agent that needs the value will reach for it.
|
|
|
|
## Decision
|
|
|
|
**When `WARDEN_AGENT_ID` is set and the catalog lane is `risk: high`, ops-warden
|
|
refuses to stream the raw value and exits 7.** The agent is not blocked from doing
|
|
its work — `--out`, `--exec`, `--wrap` and `--fingerprint` all remain available.
|
|
It is blocked from doing its work *in a way that writes the secret into a transcript*.
|
|
|
|
**Revision 2 also recognizes the issued coding-agent subject.** When
|
|
`WARDEN_POLICY_SUBJECT` is the KeyCape-issued
|
|
`service:codex:railiance-platform` identity, the same refusal applies without
|
|
`WARDEN_AGENT_ID`. The legacy variable remains a fail-toward-safety fallback.
|
|
Ops-warden reads this identity marker; it does not validate the token or decide
|
|
whether the principal is authorized.
|
|
|
|
**The boundary is enforced at the credential store as well as at the CLI.** The
|
|
OpenBao policy `agent-high-risk-boundary` denies data-read on those paths for agent
|
|
tokens, allowing metadata and capabilities only. A control that lives solely in our
|
|
own CLI is a control that ends the moment someone calls `bao` directly.
|
|
|
|
**Verification must not require a read.** To check a lane, use
|
|
`bao token capabilities` — allow/deny — never a read of the value. This is the
|
|
specific habit the disclosure taught us to break.
|
|
|
|
**Exposure is reportable without reading.** `warden taint <catalog-id>` reports KV v2
|
|
`custom_metadata` (`exposed_at`, `exposed_version`) and touches no secret data.
|
|
|
|
## Consequences
|
|
|
|
**Agents can still do everything they could before, by a different route.** `--exec`
|
|
covers nearly every real case: the child process gets the value in its environment,
|
|
the agent never sees it. The friction is deliberate and small.
|
|
|
|
**Exit 7 is a contract other runtimes depend on.** It is a distinguishable code, not
|
|
a generic failure, so a caller can tell "refused by boundary" from "lane broken" and
|
|
retry correctly. Changing it is a breaking change to every agent runtime.
|
|
|
|
**`risk: high` becomes a load-bearing catalog field** rather than documentation.
|
|
Classifying a lane now changes runtime behaviour, so it must be set deliberately.
|
|
|
|
**We accept that `--unsafe-stdout` still exists for humans.** The CLI recognizes the
|
|
issued subject when the runtime presents it and otherwise relies on
|
|
`WARDEN_AGENT_ID`; both markers remain caller-visible and therefore advisory. This
|
|
ADR raises the floor for cooperating runtimes and hardens the store behind them; it
|
|
does not claim to stop a determined caller. The OpenBao
|
|
`agent-high-risk-boundary` policy is the enforced control.
|
|
|
|
## Related
|
|
|
|
- `wiki/playbooks/agent-read-boundary.md`
|
|
- `wiki/playbooks/exposed-taint.md`
|
|
- `ADR-0002` — the conduit rule this narrows for agent callers
|