Lift ops-warden's binding rules into owned ADRs
Five rules that governed this repo lived in wiki prose, a workplan, and a
comment at the top of catalog.yaml. All were followed; none was addressable.
A reader outside ops-warden could not cite one, could not tell whether it was
current, and — the point of this change — could not tell whether it was ours
to change or someone else's that we merely obey.
ADR-0001 The routing catalog is a pointer layer, never a second copy
ADR-0002 ops-warden is a transparent conduit, never a secret broker
ADR-0003 Cover gaps, but never silently own them
ADR-0004 High-risk lanes refuse raw value streaming to agent sessions
ADR-0005 Implement one lane narrowly, route everything else
Each carries owner: ops-warden, which is the load-bearing field. It says we
follow the rule AND we are responsible for changing it — by superseding ADR,
never an in-place edit. The failure this prevents runs both ways: a rule we own
mistaken for inherited canon never gets fixed, because we wait for an owner who
does not exist; inherited canon mistaken for ours gets quietly bent, and the
drift is invisible until it breaks a repo that trusted the canonical version.
Rules we follow but do not own — NetKingdom canon, the IAM profile, the
credential-management standard, the-custodian's ADR-001 workplan convention —
are cited, never copied into docs/adr/. Copying them would recreate exactly the
second-source-of-truth failure ADR-0001 exists to prevent. architecture.md also
now flags the three-digit/four-digit ADR-001 vs ADR-0001 collision, which is
itself an ours-versus-inherited confusion waiting to happen.
Publication verified rather than assumed: all five render through policy-nexus
tools/render.py, and owner reaches the reader in three places — the page eyebrow
(render.py:346), the index Owner column (build_site.py:123,137), and the
publication manifest. build_site.py:179 makes title/status/owner required, so
ownership cannot be dropped on the way out. policy-nexus publishes and never
writes back; docs/adr/ stays the source of truth.
Documentation adapted: SCOPE.md gains a Governing rules section and an orientation
entry; .claude/rules/architecture.md replaced its stub with the ADR index, the
owned-versus-inherited rule, and ADR-over-wiki precedence; finding-routing.md's
ADR-gap section closed; catalog.yaml's no-double-source header now cites ADR-0001
rather than the originating workplan.
uv run pytest -q → 338 passed, 4 deselected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:35:13 +02:00
|
|
|
---
|
|
|
|
|
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
|
2026-09-05 01:19:48 +02:00
|
|
|
version: "1.1"
|
|
|
|
|
revision: "2"
|
Lift ops-warden's binding rules into owned ADRs
Five rules that governed this repo lived in wiki prose, a workplan, and a
comment at the top of catalog.yaml. All were followed; none was addressable.
A reader outside ops-warden could not cite one, could not tell whether it was
current, and — the point of this change — could not tell whether it was ours
to change or someone else's that we merely obey.
ADR-0001 The routing catalog is a pointer layer, never a second copy
ADR-0002 ops-warden is a transparent conduit, never a secret broker
ADR-0003 Cover gaps, but never silently own them
ADR-0004 High-risk lanes refuse raw value streaming to agent sessions
ADR-0005 Implement one lane narrowly, route everything else
Each carries owner: ops-warden, which is the load-bearing field. It says we
follow the rule AND we are responsible for changing it — by superseding ADR,
never an in-place edit. The failure this prevents runs both ways: a rule we own
mistaken for inherited canon never gets fixed, because we wait for an owner who
does not exist; inherited canon mistaken for ours gets quietly bent, and the
drift is invisible until it breaks a repo that trusted the canonical version.
Rules we follow but do not own — NetKingdom canon, the IAM profile, the
credential-management standard, the-custodian's ADR-001 workplan convention —
are cited, never copied into docs/adr/. Copying them would recreate exactly the
second-source-of-truth failure ADR-0001 exists to prevent. architecture.md also
now flags the three-digit/four-digit ADR-001 vs ADR-0001 collision, which is
itself an ours-versus-inherited confusion waiting to happen.
Publication verified rather than assumed: all five render through policy-nexus
tools/render.py, and owner reaches the reader in three places — the page eyebrow
(render.py:346), the index Owner column (build_site.py:123,137), and the
publication manifest. build_site.py:179 makes title/status/owner required, so
ownership cannot be dropped on the way out. policy-nexus publishes and never
writes back; docs/adr/ stays the source of truth.
Documentation adapted: SCOPE.md gains a Governing rules section and an orientation
entry; .claude/rules/architecture.md replaced its stub with the ADR index, the
owned-versus-inherited rule, and ADR-over-wiki precedence; finding-routing.md's
ADR-gap section closed; catalog.yaml's no-double-source header now cites ADR-0001
rather than the originating workplan.
uv run pytest -q → 338 passed, 4 deselected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:35:13 +02:00
|
|
|
owner: ops-warden
|
|
|
|
|
binds: "ops-warden; any agent runtime calling warden access"
|
|
|
|
|
created: "2026-07-20"
|
2026-09-05 01:19:48 +02:00
|
|
|
updated: "2026-09-04"
|
|
|
|
|
last_reviewed: "2026-09-04"
|
Lift ops-warden's binding rules into owned ADRs
Five rules that governed this repo lived in wiki prose, a workplan, and a
comment at the top of catalog.yaml. All were followed; none was addressable.
A reader outside ops-warden could not cite one, could not tell whether it was
current, and — the point of this change — could not tell whether it was ours
to change or someone else's that we merely obey.
ADR-0001 The routing catalog is a pointer layer, never a second copy
ADR-0002 ops-warden is a transparent conduit, never a secret broker
ADR-0003 Cover gaps, but never silently own them
ADR-0004 High-risk lanes refuse raw value streaming to agent sessions
ADR-0005 Implement one lane narrowly, route everything else
Each carries owner: ops-warden, which is the load-bearing field. It says we
follow the rule AND we are responsible for changing it — by superseding ADR,
never an in-place edit. The failure this prevents runs both ways: a rule we own
mistaken for inherited canon never gets fixed, because we wait for an owner who
does not exist; inherited canon mistaken for ours gets quietly bent, and the
drift is invisible until it breaks a repo that trusted the canonical version.
Rules we follow but do not own — NetKingdom canon, the IAM profile, the
credential-management standard, the-custodian's ADR-001 workplan convention —
are cited, never copied into docs/adr/. Copying them would recreate exactly the
second-source-of-truth failure ADR-0001 exists to prevent. architecture.md also
now flags the three-digit/four-digit ADR-001 vs ADR-0001 collision, which is
itself an ours-versus-inherited confusion waiting to happen.
Publication verified rather than assumed: all five render through policy-nexus
tools/render.py, and owner reaches the reader in three places — the page eyebrow
(render.py:346), the index Owner column (build_site.py:123,137), and the
publication manifest. build_site.py:179 makes title/status/owner required, so
ownership cannot be dropped on the way out. policy-nexus publishes and never
writes back; docs/adr/ stays the source of truth.
Documentation adapted: SCOPE.md gains a Governing rules section and an orientation
entry; .claude/rules/architecture.md replaced its stub with the ADR index, the
owned-versus-inherited rule, and ADR-over-wiki precedence; finding-routing.md's
ADR-gap section closed; catalog.yaml's no-double-source header now cites ADR-0001
rather than the originating workplan.
uv run pytest -q → 338 passed, 4 deselected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:35:13 +02:00
|
|
|
review_interval: 6m
|
2026-09-05 01:19:48 +02:00
|
|
|
enforced_by: "src/warden/access.py + src/warden/cli.py (exit 7); OpenBao policy agent-high-risk-boundary"
|
Lift ops-warden's binding rules into owned ADRs
Five rules that governed this repo lived in wiki prose, a workplan, and a
comment at the top of catalog.yaml. All were followed; none was addressable.
A reader outside ops-warden could not cite one, could not tell whether it was
current, and — the point of this change — could not tell whether it was ours
to change or someone else's that we merely obey.
ADR-0001 The routing catalog is a pointer layer, never a second copy
ADR-0002 ops-warden is a transparent conduit, never a secret broker
ADR-0003 Cover gaps, but never silently own them
ADR-0004 High-risk lanes refuse raw value streaming to agent sessions
ADR-0005 Implement one lane narrowly, route everything else
Each carries owner: ops-warden, which is the load-bearing field. It says we
follow the rule AND we are responsible for changing it — by superseding ADR,
never an in-place edit. The failure this prevents runs both ways: a rule we own
mistaken for inherited canon never gets fixed, because we wait for an owner who
does not exist; inherited canon mistaken for ours gets quietly bent, and the
drift is invisible until it breaks a repo that trusted the canonical version.
Rules we follow but do not own — NetKingdom canon, the IAM profile, the
credential-management standard, the-custodian's ADR-001 workplan convention —
are cited, never copied into docs/adr/. Copying them would recreate exactly the
second-source-of-truth failure ADR-0001 exists to prevent. architecture.md also
now flags the three-digit/four-digit ADR-001 vs ADR-0001 collision, which is
itself an ours-versus-inherited confusion waiting to happen.
Publication verified rather than assumed: all five render through policy-nexus
tools/render.py, and owner reaches the reader in three places — the page eyebrow
(render.py:346), the index Owner column (build_site.py:123,137), and the
publication manifest. build_site.py:179 makes title/status/owner required, so
ownership cannot be dropped on the way out. policy-nexus publishes and never
writes back; docs/adr/ stays the source of truth.
Documentation adapted: SCOPE.md gains a Governing rules section and an orientation
entry; .claude/rules/architecture.md replaced its stub with the ADR index, the
owned-versus-inherited rule, and ADR-over-wiki precedence; finding-routing.md's
ADR-gap section closed; catalog.yaml's no-double-source header now cites ADR-0001
rather than the originating workplan.
uv run pytest -q → 338 passed, 4 deselected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:35:13 +02:00
|
|
|
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*.
|
|
|
|
|
|
2026-09-05 01:19:48 +02:00
|
|
|
**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.
|
|
|
|
|
|
Lift ops-warden's binding rules into owned ADRs
Five rules that governed this repo lived in wiki prose, a workplan, and a
comment at the top of catalog.yaml. All were followed; none was addressable.
A reader outside ops-warden could not cite one, could not tell whether it was
current, and — the point of this change — could not tell whether it was ours
to change or someone else's that we merely obey.
ADR-0001 The routing catalog is a pointer layer, never a second copy
ADR-0002 ops-warden is a transparent conduit, never a secret broker
ADR-0003 Cover gaps, but never silently own them
ADR-0004 High-risk lanes refuse raw value streaming to agent sessions
ADR-0005 Implement one lane narrowly, route everything else
Each carries owner: ops-warden, which is the load-bearing field. It says we
follow the rule AND we are responsible for changing it — by superseding ADR,
never an in-place edit. The failure this prevents runs both ways: a rule we own
mistaken for inherited canon never gets fixed, because we wait for an owner who
does not exist; inherited canon mistaken for ours gets quietly bent, and the
drift is invisible until it breaks a repo that trusted the canonical version.
Rules we follow but do not own — NetKingdom canon, the IAM profile, the
credential-management standard, the-custodian's ADR-001 workplan convention —
are cited, never copied into docs/adr/. Copying them would recreate exactly the
second-source-of-truth failure ADR-0001 exists to prevent. architecture.md also
now flags the three-digit/four-digit ADR-001 vs ADR-0001 collision, which is
itself an ours-versus-inherited confusion waiting to happen.
Publication verified rather than assumed: all five render through policy-nexus
tools/render.py, and owner reaches the reader in three places — the page eyebrow
(render.py:346), the index Owner column (build_site.py:123,137), and the
publication manifest. build_site.py:179 makes title/status/owner required, so
ownership cannot be dropped on the way out. policy-nexus publishes and never
writes back; docs/adr/ stays the source of truth.
Documentation adapted: SCOPE.md gains a Governing rules section and an orientation
entry; .claude/rules/architecture.md replaced its stub with the ADR index, the
owned-versus-inherited rule, and ADR-over-wiki precedence; finding-routing.md's
ADR-gap section closed; catalog.yaml's no-double-source header now cites ADR-0001
rather than the originating workplan.
uv run pytest -q → 338 passed, 4 deselected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:35:13 +02:00
|
|
|
**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.
|
|
|
|
|
|
2026-09-05 01:19:48 +02:00
|
|
|
**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.
|
Lift ops-warden's binding rules into owned ADRs
Five rules that governed this repo lived in wiki prose, a workplan, and a
comment at the top of catalog.yaml. All were followed; none was addressable.
A reader outside ops-warden could not cite one, could not tell whether it was
current, and — the point of this change — could not tell whether it was ours
to change or someone else's that we merely obey.
ADR-0001 The routing catalog is a pointer layer, never a second copy
ADR-0002 ops-warden is a transparent conduit, never a secret broker
ADR-0003 Cover gaps, but never silently own them
ADR-0004 High-risk lanes refuse raw value streaming to agent sessions
ADR-0005 Implement one lane narrowly, route everything else
Each carries owner: ops-warden, which is the load-bearing field. It says we
follow the rule AND we are responsible for changing it — by superseding ADR,
never an in-place edit. The failure this prevents runs both ways: a rule we own
mistaken for inherited canon never gets fixed, because we wait for an owner who
does not exist; inherited canon mistaken for ours gets quietly bent, and the
drift is invisible until it breaks a repo that trusted the canonical version.
Rules we follow but do not own — NetKingdom canon, the IAM profile, the
credential-management standard, the-custodian's ADR-001 workplan convention —
are cited, never copied into docs/adr/. Copying them would recreate exactly the
second-source-of-truth failure ADR-0001 exists to prevent. architecture.md also
now flags the three-digit/four-digit ADR-001 vs ADR-0001 collision, which is
itself an ours-versus-inherited confusion waiting to happen.
Publication verified rather than assumed: all five render through policy-nexus
tools/render.py, and owner reaches the reader in three places — the page eyebrow
(render.py:346), the index Owner column (build_site.py:123,137), and the
publication manifest. build_site.py:179 makes title/status/owner required, so
ownership cannot be dropped on the way out. policy-nexus publishes and never
writes back; docs/adr/ stays the source of truth.
Documentation adapted: SCOPE.md gains a Governing rules section and an orientation
entry; .claude/rules/architecture.md replaced its stub with the ADR index, the
owned-versus-inherited rule, and ADR-over-wiki precedence; finding-routing.md's
ADR-gap section closed; catalog.yaml's no-double-source header now cites ADR-0001
rather than the originating workplan.
uv run pytest -q → 338 passed, 4 deselected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:35:13 +02:00
|
|
|
|
|
|
|
|
## Related
|
|
|
|
|
|
|
|
|
|
- `wiki/playbooks/agent-read-boundary.md`
|
|
|
|
|
- `wiki/playbooks/exposed-taint.md`
|
|
|
|
|
- `ADR-0002` — the conduit rule this narrows for agent callers
|