ops-warden/history/2026-07-16-credential-disclosure-lessons.md
tegwick ea98d6bf39
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
WARDEN-WP-0026 T01: capabilities-safe lane verification + incident note
T01 (done): canonical capabilities-based verify pattern in the fleet promotion
checklist (catalog-lane-promotion.md) and applied to the railiance-backup and
forgejo-admin lane playbooks. Verification proves allow/deny via
`bao token capabilities` against the KV v2 data path, never `bao kv get`; a denied
default-policy token-create is a pass, not a privileged-fallback trigger.

T07 (progress): lessons-learned note for the 2026-07-16 CCR-2026-0004 disclosure
(three root causes). Live re-verify + rotation block remain (depend on T06).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:26:05 +02:00

57 lines
2.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Credential disclosure lessons — 2026-07-16
**Context:** buildup mode. Exposure was accepted; the value here is the learnings,
not blame. Rotation of the exposed values is the operator's optional call, not a
blocker (see WP-0026 T07).
## What happened
While verifying `CCR-2026-0004` (railiance offsite backup lane), a negative policy
test was run as:
```bash
BAO_TOKEN=$(bao token create -policy=default -field=token) bao kv get <path>
```
The `bao token create` was **denied** (the workload role lacks it), so `BAO_TOKEN`
was left unset and `bao kv get` fell back to the caller's **privileged login
token**. The read succeeded and printed all three field values —
`NC_WEBDAV_TOKEN`, `NC_WEBDAV_URL`, `AGE_PRIVATE_KEY` — into an agent session
transcript (a logged context).
## Root causes
1. **The deny-test read the secret data path at all.** A negative test should
prove *deny*, and proving deny never requires reading the value.
2. **Silent privileged-token fallback.** When the scoped token creation failed,
the command quietly used the caller's privileged token instead of failing.
3. **The read landed in a logged context.** An agent session transcript is not a
safe sink for secret material.
## Corrections (WARDEN-WP-0026, Strand A)
- **Verification never reads secret data.** Prove allow/deny with
`bao token capabilities`, not `bao kv get`. If `bao token create -policy=default`
is itself denied, that is a *pass* for the deny direction — never fall back to a
privileged token. Canonical pattern:
`wiki/playbooks/catalog-lane-promotion.md#capabilities-safe-lane-verification`
(WP-0026 T01, applied to the forgejo and railiance-backup lane playbooks).
- **Safe transport** for values that must move: env var, file, or response-wrapping
token (`-wrap-ttl`) — never a stdout table (WP-0026 T02).
- **Masking** as defense-in-depth in the warden wrapper (WP-0026 T03).
- **Agent read-boundary + EXPOSED taint** on high-risk lanes, and per-lane
**rotation guidance** (WP-0026 T04T06).
## Deferred (Strand B — WARDEN-WP-0027)
Executable one-command mass rotation, graded lockdown / break-glass with a designed
trust-root, and tamper-evident policy governance + reconcile are captured in
`WARDEN-WP-0027` (backlog, gated on an activation trigger).
## References
- `WARDEN-WP-0026` — disclosure hygiene (Strand A)
- `WARDEN-WP-0027` — governance/lockdown (Strand B, deferred)
- `CCR-2026-0004-railiance-backup-offsite-lane.yaml` (railiance-platform)
- `wiki/playbooks/railiance-backup-offsite-lane.md`
- `.claude/rules/credential-routing.md`