Files
ops-warden/wiki/OperatorAccessAssist.md
tegwick 5bbb791f21 docs(WARDEN-WP-0014): T5 — assist-layer docs, security model, INTENT/SCOPE
- 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>
2026-06-27 17:35:57 +02:00

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