From 7e0789ab0d41e8276f835d05624a7a48f093fc03 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 16 Jul 2026 01:41:39 +0200 Subject: [PATCH] WARDEN-WP-0026: credential disclosure hygiene + rotation guidance (Strand A) Follow-up to the 2026-07-16 CCR-2026-0004 verify disclosure incident. Strand A: capabilities-based verification, safe access transport, masking (defense-in-depth), agent read-boundary, EXPOSED taint convention, and a structured-but-advisory rotation/re-establishment guidance registry surfaced via warden. Strand B deferred. Co-Authored-By: Claude Opus 4.8 --- ...N-WP-0026-credential-disclosure-hygiene.md | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md diff --git a/workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md b/workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md new file mode 100644 index 0000000..3adfcb6 --- /dev/null +++ b/workplans/WARDEN-WP-0026-credential-disclosure-hygiene.md @@ -0,0 +1,188 @@ +--- +id: WARDEN-WP-0026 +type: workplan +title: "Credential disclosure hygiene + rotation guidance (Strand A)" +domain: infotech +repo: ops-warden +status: ready +owner: codex +topic_slug: custodian +planning_priority: high +planning_order: 26 +created: "2026-07-16" +updated: "2026-07-16" +--- + +# Credential disclosure hygiene + rotation guidance (Strand A) + +## Origin + +Follow-up to a credential-disclosure incident on 2026-07-16 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 `. +The `token create` was denied (workload role lacks it), `BAO_TOKEN` fell back to +the caller's privileged login token, and `bao kv get` printed all three field +values (`NC_WEBDAV_TOKEN`, `NC_WEBDAV_URL`, `AGE_PRIVATE_KEY`) into an agent +session transcript. Buildup mode — exposure accepted, learnings captured. + +**Root causes:** (1) a deny-test that read the secret *data* path at all; +(2) a silent privileged-token fallback; (3) the read landed in a logged context. + +This is **Strand A** (disclosure hygiene). Tamper-resistant policy governance and +one-command mass rotation/lockdown (Strand B) are deliberately **deferred** — see +"Out of scope" below. + +## Goal + +Make accidental secret disclosure structurally hard, and make ops-warden the +authoritative source for **how each secret is rotated or re-established** — +advisory knowledge held next to the routing catalog, not in OpenBao. + +## Design guardrails (binding on acceptance) + +- **Verification never reads secret data.** Use `bao token capabilities` (allow/deny) + instead of `kv get` for positive/negative lane tests. +- **Masking is defense-in-depth, not a boundary.** Any display filter is a wrapper + convenience; raw `bao kv get ` remains the documented anti-pattern. +- **Rotation guidance covers both `rotate` (provider re-mint) and `re-establish` + (regenerate from source, e.g. a new age keypair).** +- **Coverage gate:** every `active` (and newly promoted) catalog lane must carry + rotation guidance, enforced by a scorecard check. + +## Out of scope (Strand B — deferred, not built here) + +- Executable one-command rotation of all tainted secrets. +- Graded lockdown / break-glass seal + re-key with a designed trust-root. +- Policy time-travel / policy-as-code reconcile-to-past-commit. (Drift *detection* + may be revisited separately; the reconcile machinery is out.) + +## Task: Capabilities-based lane verification + +```task +id: WARDEN-WP-0026-T01 +status: todo +priority: high +``` + +Replace secret-reading verify flows with capability checks. Positive test: +approved identity has `read` on the KV data path. Negative test: a `default`-only +identity is `deny`. Both via `bao token capabilities ` (or the +self endpoint) — **never** `bao kv get`. Update +`wiki/playbooks/*lane*.md` verify sections and any lane-verify helper. + +**Done when:** the documented and tooled verify path for any lane proves +allow/deny without reading a secret value, and the CCR-2026-0004 re-verify uses +it (see T07). + +## Task: Safe access transport (no stdout values) + +```task +id: WARDEN-WP-0026-T02 +status: todo +priority: high +``` + +`warden access` / fetch paths must emit values only into an env var, a file, or +a **response-wrapping token** (`bao … -wrap-ttl`), never a stdout table. Add a +wrapping-token transport for values that must move between processes. Record in +canon (`credential-routing` rules) that raw `bao kv get ` (full table) is +the anti-pattern; the sanctioned path is `warden access … --field ` into env. + +**Done when:** the sanctioned fetch path cannot print a value to a terminal, and +the anti-pattern is documented fleet-wide. + +## Task: Masking display filter (defense-in-depth) + +```task +id: WARDEN-WP-0026-T03 +status: todo +priority: medium +``` + +In the warden wrapper, mask KV data values by default when any listing/status is +shown — display presence, length, and a short non-reversible hash instead of the +value. Explicitly labelled as defense-in-depth (raw bao bypasses it). + +**Done when:** wrapper-mediated output never shows a raw KV value, with the +limitation documented. + +## Task: Agent read-boundary on high-risk lanes + +```task +id: WARDEN-WP-0026-T04 +status: todo +priority: high +``` + +**Repo: railiance-platform (OpenBao policy/roles).** Agent identities receive +`capabilities`/metadata and wrapping tokens on high-risk lanes, not raw data +reads. Align with the existing credential-routing rule ("ops-warden proxies reads +as the caller and must not retain values"). Classify which lanes are high-risk +(recovery escrow like `AGE_PRIVATE_KEY`, upload tokens). + +**Done when:** at least the high-risk lanes deny raw data reads to agent roles +while still allowing wrapped/proxied access, verified via capabilities checks. + +## Task: EXPOSED taint convention + +```task +id: WARDEN-WP-0026-T05 +status: todo +priority: medium +``` + +**Repo: railiance-platform (OpenBao) + ops-warden surface.** Establish a KV v2 +`custom_metadata` convention to mark a tainted secret: `exposed_at=` +and the affected `version`. Identify semi-automatic candidates from the OpenBao +audit log (reads from agent/shared contexts). `warden` surfaces taint status for +a lane (advisory; no auto-rotation here). + +**Done when:** a secret can be marked EXPOSED via a documented convention and +`warden` reports whether a lane is currently tainted. + +## Task: Rotation / re-establishment guidance registry + +```task +id: WARDEN-WP-0026-T06 +status: todo +priority: high +``` + +Give every catalog lane **structured-but-advisory** renewal guidance, held in the +ops-warden registry (not in OpenBao). Add a `rotation:` block per catalog entry +capturing: `method` (rotate | re-establish), ordered `steps` (provider re-mint / +keygen / OpenBao write / re-encrypt-and-reupload where relevant), `owner`, and +`automatable` (bool, for future Strand-B). Surface via `warden rotate-guide ` +(and/or `warden route … --rotate`). Add a **scorecard coverage check**: every +`active` lane must have a `rotation:` block; flag any that don't. + +**Done when:** `warden rotate-guide ` returns actionable renewal steps for +every active lane, and the scorecard fails if any active lane lacks guidance. + +## Task: Incident lessons + first worked lane (CCR-2026-0004) + +```task +id: WARDEN-WP-0026-T07 +status: todo +priority: medium +``` + +Write a short lessons-learned note (buildup context; exposure accepted; the three +root causes). Apply T01 + T06 to `CCR-2026-0004` as the first worked lane: +re-verify it the capabilities-safe way so it can finally promote to +`resolvable: true` (unblocking `RAILIANCE-WP-0015`), and ensure its `rotation:` +block (rotate Nextcloud token; re-establish age keypair + re-encrypt artifacts) +is present. **Rotation of the exposed values is the operator's optional call, not +a blocker** (buildup). + +**Done when:** the lessons note exists, CCR-2026-0004 has capabilities-based +verify + rotation guidance, and its promotion path is unblocked. + +## References + +- `CCR-2026-0004-railiance-backup-offsite-lane.yaml` (railiance-platform) +- `wiki/playbooks/railiance-backup-offsite-lane.md` +- `.claude/rules/credential-routing.md` (the-custodian, fleet-inlined) +- `RAILIANCE-WP-0015` (railiance-apps) — cnpg backup coverage, gated on CCR-2026-0004 +- Strand B (deferred): tamper-resistant governance, one-command rotation/lockdown, + policy time-travel — capture separately if/when justified.