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

2.5 KiB
Raw Permalink Blame History

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:

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