- wiki/OperatorAccessAssist.md: warden access contract, conduit-vs-broker boundary, the three guardrails + catalog secret guard, lane semantics. - AccessRouting.md: issue/route/assist roles; reconciled the anti-pattern table so the transparent conduit no longer contradicts it. - credential-routing.md rule: added warden access + "standing broker forbidden, transparent --fetch sanctioned" anti-pattern. - INTENT.md: pointer→assist charter extension. SCOPE.md: implemented list + Getting Oriented + maturity A4→A5 (Availability). - history decision record for the proxy-mode choice and guardrails. WP-0014 finished (T1–T5). 172 passed, lint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.6 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
# proxy a secret read as the caller (gated + audited); value streams to stdout
$ warden access "npm token" --domain coulomb_social --field NPM_AUTH_TOKEN --path <p> --fetch
# 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
# interactive login (login lane): no token required, no secret-read gate
$ warden access "login oidc" --domain coulomb_social --fetch
--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 | --fetch runs with inherited stdout (never a pipe), so the value streams to the caller and never enters warden's memory. --exec reads the value solely to place it in a child process's env (the accepted --exec tradeoff) — never to disk or log. The audit record is metadata only. |
| G3 | Policy gate before fetch | check_fetch_policy (flex-auth) runs before any secret-lane fetch. With policy.enabled: false the proxy refuses unless --no-policy is given to acknowledge proxying ungated. |
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