91 lines
3.8 KiB
Markdown
91 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
|