WARDEN-WP-0026 T02: safe access transports (no secret values on stdout)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s

- proxy.py: proxy_fetch_to_file (mode-0600 file), build_wrapped_fetch +
  proxy_fetch_wrapped (single-use OpenBao response-wrapping token), _capture_value
  helper, is_bao_kv_fetch.
- warden access: --out FILE, --wrap [--wrap-ttl], --unsafe-stdout. Raw --fetch to a
  non-TTY stdout is refused (exit 6) — captured/piped output is the disclosure risk;
  sanctioned transports are --out / --exec / --wrap.
- canon: anti-pattern (secret value onto captured stdout) + transport table in
  .claude/rules/credential-routing.md; OperatorAccessAssist.md examples + G2 updated.
- tests: file/wrap/build + stdout-guard in tests/test_proxy.py. 293 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-16 14:51:56 +02:00
parent c749561b75
commit 359ca1bd0e
6 changed files with 281 additions and 11 deletions

View file

@ -22,14 +22,21 @@ audited"). It does **not** move secret custody into ops-warden.
```console
# 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
# --- 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
```
> **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`.
`--json` gives a stable, secret-free shape for agentic operators.
---
@ -60,7 +67,7 @@ prevent, and duplicates OpenBao.
| | 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**. |
| **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 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,