# 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_capable` lanes — 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 ` | Renders the owner, auth method, path, command skeleton, policy gate | | **Assist (proxy)** | `exec_capable` lanes (OpenBao, login) | `warden access --fetch / --exec` | Runs the owner's tool **as the caller**; value never touches warden | ```console # 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

--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

--wrap # then: bao unwrap # 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

--fingerprint # ‹hidden len=40 sha256:1a2b3c4d› ``` > **Raw `--fetch` to stdout is the anti-pattern.** It is refused when stdout is > captured or piped (a logged-context disclosure risk); pass `--unsafe-stdout` only > for an interactive human terminal. Prefer `--out` / `--exec` / `--wrap`. > **`--fingerprint` is defense-in-depth, not a boundary.** It masks warden-mediated > output so two parties can compare `sha256` prefixes to confirm they hold the same > value (e.g. that a rotation landed) without either seeing it. It only masks > *warden's* output — raw `bao kv get ` 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; `--exec` is rejected; the token lands in the caller's own store and warden never captures it. --- ## What proxying requires - An `exec_capable` catalog entry with a resolvable `fetch_command`. - For `secret` lanes: the caller already authenticated (`VAULT_TOKEN`/`BAO_TOKEN` or `~/.vault-token`) and a loadable `warden.yaml` (for policy posture + audit sink). - All `<…>` placeholders resolved — `warden access` **refuses to run a half-templated command** rather than guess an owner-confirmed resource name. Supply `--domain`, `--field`, and `--path` as 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 roles - `wiki/CredentialRouting.md` — which subsystem owns each need - `registry/routing/catalog.yaml` — handoff fields + lanes - `wiki/PolicyGatedSigning.md` — the flex-auth gate (shared with the SSH lane) - `.claude/rules/credential-routing.md` — agent-facing routing + anti-patterns - `history/2026-06-27-operator-access-assist-charter.md` — the proxy-mode decision