WARDEN-WP-0026 T01: capabilities-safe lane verification + incident note
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

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>
This commit is contained in:
tegwick 2026-07-16 14:26:05 +02:00
parent 2ad8a53781
commit ea98d6bf39
5 changed files with 161 additions and 3 deletions

View file

@ -22,12 +22,50 @@ Before changing `status: draft` → `status: active`:
| 5 | **Resolvable** | `warden route show <id> --json` shows `resolvable: true` when placeholders are documented |
| 6 | **Tests** | Routing test or smoke proving lookup + handoff shape (no secret values in fixtures) |
| 7 | **Review date** | Update `reviewed:` in catalog entry |
| 8 | **Verification** | Positive + negative proof via **`bao token capabilities`** — never `bao kv get` (see below) |
Promotion PR touches: `registry/routing/catalog.yaml`, playbook, optional
`tests/test_routing.py`, and a one-line note in `wiki/CredentialRouting.md` draft table.
---
## Capabilities-safe lane verification (WARDEN-WP-0026 T01)
**Verifying a lane must never read the secret *data*.** A negative deny-test that
runs `bao kv get <path>` will, if the deny fails (e.g. a privileged token
fallback), print the secret value into a logged context — this is exactly the
2026-07-16 CCR-2026-0004 disclosure. Prove *allow/deny* with
`bao token capabilities`, which returns the capability list, not the value.
For KV v2, capabilities are checked against the **API data path**
(`<mount>/data/<lane-path>`), not the `kv get` logical path.
```bash
# Positive: the lane's own OIDC identity can read the data path.
bao login -method=oidc -path=netkingdom role=<lane-role> # caller identity
bao token capabilities "$(bao print token)" platform/data/<lane-path>
# → expect the list to include: read
# Negative: a default-only identity is denied — no value is ever read.
DEFAULT_TOKEN=$(bao token create -policy=default -field=token) # if denied, STOP — do not fall back
bao token capabilities "$DEFAULT_TOKEN" platform/data/<lane-path>
# → expect: deny
```
- **Never** substitute `bao kv get` for the checks above. Reading a value to
"confirm it's there" is the anti-pattern; presence is proven by `read` in the
capability list.
- If `bao token create -policy=default` is itself denied for your identity, that
is a *pass for the deny direction***do not** fall back to your privileged
login token to force the read.
- Fetching a value **for use** (`--field` into an env var or file, or
`warden access … --field`) is a separate, intended action — not verification.
Record the capability lists (allow/deny) as the promotion evidence; they contain
no secret material and are safe for CCRs, State Hub, and Git.
---
## Worked examples (already active)
**`ops-warden-warden-sign-token`** — promoted 2026-07-01 after RAILIANCE-WP-0005:

View file

@ -95,6 +95,30 @@ After CCR approval and policy apply:
---
## Verify the lane (capabilities-safe — never read the value)
Prove allow/deny with `bao token capabilities`, **not** `bao kv get -field=…`.
`bao kv metadata get` (above) is fine — it shows versions, not values. Reading the
data field to "confirm" it is the anti-pattern
(`wiki/playbooks/catalog-lane-promotion.md#capabilities-safe-lane-verification`).
```bash
# Positive: lane OIDC identity can read the data path
bao login -method=oidc -path=netkingdom role=forgejo-admin-workload-kv-read
bao token capabilities "$(bao print token)" platform/data/workloads/forgejo/forgejo-admin
# → expect: read
# Negative: default-only identity is denied
DEFAULT_TOKEN=$(bao token create -policy=default -field=token) # if denied, that IS the pass — do NOT fall back
bao token capabilities "$DEFAULT_TOKEN" platform/data/workloads/forgejo/forgejo-admin
# → expect: deny
```
Confirming the PAT works against Forgejo is a separate, value-using action — fetch
`--field API_TOKEN` into an env var and call `/api/v1/user`; never paste the token.
---
## Consumers (downstream wiring — after lane verified)
| Consumer | Repo |

View file

@ -56,4 +56,28 @@ Used by `railiance-backup` (workstation) and `forgejo-backup` (platform).
tools/cmd/forgejo-backup
```
`AGE_PRIVATE_KEY` in the same path is recovery escrow — fetch only for restore drills.
`AGE_PRIVATE_KEY` in the same path is recovery escrow — fetch only for restore drills.
---
## Verify the lane (capabilities-safe — never read the value)
Prove allow/deny with `bao token capabilities`, **not** `bao kv get`. Reading the
value to "confirm" it triggered the 2026-07-16 disclosure of `NC_WEBDAV_TOKEN` /
`NC_WEBDAV_URL` / `AGE_PRIVATE_KEY` (see `history/2026-07-16-credential-disclosure-lessons.md`).
```bash
# Positive: lane OIDC identity can read the data path
bao login -method=oidc -path=netkingdom role=railiance-backup-workload-kv-read
bao token capabilities "$(bao print token)" platform/data/workloads/railiance/backup/offsite-lane
# → expect: read
# Negative: default-only identity is denied (no value is read)
DEFAULT_TOKEN=$(bao token create -policy=default -field=token) # if this is denied, that IS the pass — do NOT fall back
bao token capabilities "$DEFAULT_TOKEN" platform/data/workloads/railiance/backup/offsite-lane
# → expect: deny
```
The capability lists contain no secret material — safe to record on
`CCR-2026-0004` as promotion evidence. Full pattern:
`wiki/playbooks/catalog-lane-promotion.md#capabilities-safe-lane-verification`.