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>
90 lines
3.8 KiB
Markdown
90 lines
3.8 KiB
Markdown
---
|
|
id: ops-warden-adr-0002
|
|
type: adr
|
|
title: "ADR-0002 — ops-warden is a transparent conduit, never a secret broker"
|
|
domain: infotech
|
|
repo: ops-warden
|
|
status: accepted
|
|
version: "1.0"
|
|
revision: "1"
|
|
owner: ops-warden
|
|
binds: "ops-warden"
|
|
created: "2026-06-26"
|
|
updated: "2026-08-18"
|
|
last_reviewed: "2026-08-18"
|
|
review_interval: 6m
|
|
enforced_by: "src/warden/access.py; wiki/OperatorAccessAssist.md"
|
|
supersedes: ""
|
|
successor: ""
|
|
---
|
|
|
|
# ADR-0002 — ops-warden is a transparent conduit, never a secret broker
|
|
|
|
## Status
|
|
|
|
Accepted. Decided during WARDEN-WP-0014 (operator access assist), tightened by
|
|
WARDEN-WP-0026 (disclosure hygiene).
|
|
|
|
## Context
|
|
|
|
`warden access` is the operator front door for every credential need in the estate.
|
|
For lanes marked `exec_capable` it does more than advise: it runs the owner's tool
|
|
and returns the value. Anything that fetches secrets on request looks like a broker,
|
|
and the gravity toward becoming one is strong — a broker is more convenient at every
|
|
individual call site.
|
|
|
|
The distinction is not stylistic. A broker holds authority; a conduit borrows the
|
|
caller's. Only one of those creates a new thing worth attacking.
|
|
|
|
## Decision
|
|
|
|
**ops-warden runs the owner's tool with the caller's own identity, and takes no
|
|
custody of the value.** The caller's credentials do the work. ops-warden holds
|
|
nothing after the command returns, stores nothing, and caches nothing.
|
|
|
|
**Forbidden: a standing broker.** ops-warden must not hold its own long-lived
|
|
secret-read credential in order to serve values to callers who could not have
|
|
fetched them themselves. If the caller lacks authority, the correct outcome is a
|
|
denial from the owner's system — not a fetch performed on their behalf by a more
|
|
privileged intermediary.
|
|
|
|
The test is a question: *could the caller have run this themselves?* If yes, we are
|
|
a conduit and may proxy. If no, proxying is privilege laundering and is refused.
|
|
|
|
**Owner-native front doors outrank the proxy.** Where an owner has shipped their own
|
|
exec surface — `secrets-engine exec`, the railiance-platform credential broker — we
|
|
route there and do not proxy. The proxy is a fallback for lanes nobody fronts yet,
|
|
not a preferred path. This is why `whynot-design-npm-publish` and
|
|
`ops-warden-warden-sign-token` are `native` rather than `interim`.
|
|
|
|
**The value must not land somewhere it will be logged.** Sanctioned transports are
|
|
`--out` (mode-0600 file), `--exec` (child process env), and `--wrap` (a single-use
|
|
OpenBao wrapping token). Streaming to a non-terminal stdout is refused without an
|
|
explicit `--unsafe-stdout`, which exists for interactive humans only.
|
|
|
|
## Consequences
|
|
|
|
**ops-warden never becomes a credential store, and gains no value by being
|
|
compromised beyond the SSH CA it already holds.** This is the whole point. An
|
|
attacker who owns ops-warden gets the SSH signing lane — serious, bounded, and
|
|
already the thing this repo is hardened around — not a key to every secret in the
|
|
estate.
|
|
|
|
**Some requests cannot be served, and that is the correct answer.** When a caller
|
|
lacks authority, ops-warden routes and explains rather than fetching. This reads as
|
|
unhelpfulness at the moment it happens; it is the property that makes the front door
|
|
safe to point every agent at.
|
|
|
|
**Every proxied fetch is auditable and attributable to the caller**, because it ran
|
|
as them. `audit.jsonl` records metadata only — never values, guarded in code.
|
|
|
|
**The `--unsafe-stdout` escape hatch is a known liability.** It exists because
|
|
humans in terminals legitimately need to see values. It is also exactly the shape of
|
|
the 2026-07-16 disclosure, where a value reached a captured stdout. `ADR-0004`
|
|
constrains it further for agent sessions.
|
|
|
|
## Related
|
|
|
|
- `wiki/OperatorAccessAssist.md#the-conduit-vs-broker-boundary-the-security-model`
|
|
- `ADR-0004` — the agent-session read boundary built on top of this
|
|
- `ADR-0003` — why proxied lanes are tracked as interim rather than owned
|