Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0291a-1e87-7151-9934-fcbfe3f65eb1
6.8 KiB
Operator Access Assist — warden access
The operator front door for every NetKingdom credential need. ops-warden issues the SSH lane directly and assists with the rest: it tells you exactly how to obtain a credential and — for
exec_capablelanes — proxies the fetch as you, without ever holding, persisting, or logging the value.
Shipped in WARDEN-WP-0014. This extends the routing charter from a pointer layer ("who owns it") to an assist layer ("here is exactly how to get it, gated and audited"). It does not move secret custody into ops-warden.
Three roles, one front door
| Role | Lane | Command | What ops-warden does |
|---|---|---|---|
| Issue | SSH cert (adm/agt/atm) |
warden access ssh… → warden sign |
Executes — signs the cert |
| Assist (advise) | any credential need | warden access <need> |
Renders the owner, auth method, path, command skeleton, policy gate |
| Assist (proxy) | exec_capable lanes (OpenBao, login) |
warden access <need> --fetch / --exec |
Runs the owner's tool as the caller; value never touches warden |
# advisory — works with no config; never fetches a value
$ warden access "npm token" --domain coulomb_social
# --- sanctioned transports (WP-0026 T02): value never hits stdout ---
# write the value to a mode-0600 file
$ warden access "npm token" --domain coulomb_social --field NPM_AUTH_TOKEN --path <p> --fetch --out ./npm.token
# run a child command with the secret in its env only (à la `op run`)
$ warden access "npm token" --field NPM_AUTH_TOKEN --exec -- npm publish
# return a single-use OpenBao wrapping token to unwrap in your own context
$ warden access "npm token" --path <p> --wrap # then: bao unwrap <token>
# interactive login (login lane): no token required, no secret-read gate
$ warden access "login oidc" --domain coulomb_social --fetch
# masked status: presence, length, short hash — never the value (WP-0026 T03)
$ warden access "npm token" --path <p> --fingerprint # ‹hidden len=40 sha256:1a2b3c4d›
Raw
--fetchto stdout is the anti-pattern. It is refused when stdout is captured or piped (a logged-context disclosure risk); pass--unsafe-stdoutonly for an interactive human terminal. Prefer--out/--exec/--wrap.
--fingerprintis defense-in-depth, not a boundary. It masks warden-mediated output so two parties can comparesha256prefixes to confirm they hold the same value (e.g. that a rotation landed) without either seeing it. It only masks warden's output — rawbao kv get <path>bypasses it entirely. The real boundary is OpenBao policy plus capabilities-safe verify (T01) and the no-stdout transports.
--json gives a stable, secret-free shape for agentic operators.
The conduit-vs-broker boundary (the security model)
There are two very different things "secret transits warden" can mean. One is
sanctioned; the other is forbidden by the NetKingdom responsibility model
(net-kingdom/docs/responsibility-map.md: ops-warden "must not become a universal
secret broker — runtime secrets remain OpenBao; authorization remains flex-auth").
Sanctioned — transparent conduit. ops-warden runs the owner's tool with the
caller's own identity, streams the value straight to the caller, and retains
nothing. It holds no standing credential and stores no value. This is the vault exec
/ op run shape.
Forbidden — standing broker. ops-warden holding its own long-lived secret-read token, caching fetched values, becoming a service every operator's secrets flow through and rest in. That recreates the single high-value target the model exists to prevent, and duplicates OpenBao.
warden access is built as the first and forbids the second by construction.
The three guardrails (enforced in code)
| Guardrail | How it is enforced | |
|---|---|---|
| G1 | Caller identity, never warden's | The proxy runs the owner's tool with the caller's own environment; ops-warden injects no token of its own. Secret lanes require the caller to already hold a credential (caller_auth_present), else they fail with the auth pointer. |
| G2 | Transit only — no persistence/logging of values | Sanctioned transports keep the value off stdout: --out writes it to a mode-0600 file, --exec injects it into a child process env, --wrap returns a single-use OpenBao wrapping token (not the value). Raw --fetch to stdout is refused for captured/piped output (--unsafe-stdout overrides for a human terminal). warden never writes the value to disk or log; the audit record is metadata only. (WP-0026 T02) |
| G3 | Policy gate before fetch | check_fetch_policy (flex-auth) runs before every secret-lane fetch. Zone membership selects stance and local failure mode; an unresolved workload uses the explicit unknown profile. The retired --no-policy flag is rejected, so this gate has no CLI bypass. |
The catalog side enforces a fourth, upstream guard: handoff fields are templates,
never values. _assert_no_secret_material rejects any known token prefix or
high-entropy run in a catalog handoff field, so a secret can never leak into the
git-tracked, agent-visible catalog.
Lanes
Each catalog entry declares a lane:
secret(default) — read a value. Requires caller auth (G1) and runs the flex-auth secret-read gate (G3). Value transits via inherit-stdout (--fetch) or child env (--exec).login— interactive auth bootstrap (OIDC/MFA). No caller-auth precheck (you have no token yet — that is the point) and no secret-read gate (it establishes the identity the gate would need). Runs interactively as the caller;--execis rejected; the token lands in the caller's own store and warden never captures it.
What proxying requires
- An
exec_capablecatalog entry with a resolvablefetch_command. - For
secretlanes: the caller already authenticated (VAULT_TOKEN/BAO_TOKENor~/.vault-token) and a loadablewarden.yaml(for policy posture + audit sink). - All
<…>placeholders resolved —warden accessrefuses to run a half-templated command rather than guess an owner-confirmed resource name. Supply--domain,--field, and--pathas needed.
Audit lands in state_dir/access-audit.log (JSON lines, metadata only: who, need id,
owner, domain, action, policy decision id — never a value).
See also
wiki/AccessRouting.md— issue / route / assist roleswiki/CredentialRouting.md— which subsystem owns each needregistry/routing/catalog.yaml— handoff fields + laneswiki/PolicyGatedSigning.md— the flex-auth gate (shared with the SSH lane).claude/rules/credential-routing.md— agent-facing routing + anti-patternshistory/2026-06-27-operator-access-assist-charter.md— the proxy-mode decision