The runner was executable with no shebang, so a shell handed its path tried to interpret Python as shell script. That is what a broken command line hit, and it would have hit anyone invoking the file directly. Also adds `make openbao-open-questions`, which is short enough that terminal wrapping cannot split it, refuses a receipt path that is missing or already exists, and clears OPENBAO_TOKEN/VAULT_TOKEN/BAO_TOKEN before invoking the attended envelope so the identity comes from the login rather than a standing token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLUjpv3ssxNRAEPPgLFnEB Assistant: claude-code Assistant-Model: opus Assistant-Process: 1275505@bnt-lap001 Assistant-Session: 97265baa-f08f-4032-b290-a1e2965a69c5
129 lines
7.5 KiB
Markdown
129 lines
7.5 KiB
Markdown
# Attended OpenBao session — four open custody questions
|
|
|
|
One founder-attended session that settles the questions currently blocking four
|
|
owner threads. Everything in it is read-only against OpenBao. It provisions
|
|
nothing, approves nothing, and rotates nothing.
|
|
|
|
Prepared 2026-09-09 under RPF-WP-0035. Not yet run.
|
|
|
|
## Why a session rather than a reply
|
|
|
|
Four owner threads are each blocked on a fact that only an authenticated read can
|
|
establish, and each has been answered so far with "I will not guess":
|
|
|
|
| Question | Thread | What is undecidable from repo source |
|
|
| --- | --- | --- |
|
|
| Q1 | secrets-engine `546403e4` | Whether the legacy path `secret/coulomb/whynot-design/npm/publish` still exists and holds a real token |
|
|
| Q2 | secrets-engine `546403e4`, ops-warden `b6212006` | Which field backs the authoritative npm lane. Two dated live receipts here name `NPM_AUTH_TOKEN`; secrets-engine says `npm_token` and ops-warden has already changed their catalog to it. Both can be true if the path carries both fields. Field names are not visible in KV v2 metadata |
|
|
| Q3 | internal | The 2026-09-09 activation receipt records `policy_applied: false` for both KeyCape approval lanes while `role_applied: true`. Reads verified, so the policies must exist — but existence and repo agreement were never confirmed |
|
|
| Q4 | CCR-2026-0019 | Which netkingdom OIDC roles and bound group claims already exist, as input to the operator group claim that lane is missing |
|
|
| Q5 | risk-nexus `ee702ac9` | Which fields the governed backup lane actually carries, as RPF-WP-0029-T02 context |
|
|
|
|
## Authority and containment
|
|
|
|
Authority is the governed `openbao-platform-admin-login` lane: `founder_required`,
|
|
attended OIDC through `bao login -method=oidc -path=netkingdom role=platform-admin`.
|
|
The owner command is never run directly.
|
|
|
|
```sh
|
|
make openbao-open-questions SESSION_RECEIPT=/tmp/<new-unique-name>.json
|
|
```
|
|
|
|
Use the Make target rather than typing the underlying command: it is short enough
|
|
not to be broken by terminal wrapping, it refuses a `SESSION_RECEIPT` that already
|
|
exists or is missing, and it clears any ambient token before invoking the
|
|
envelope.
|
|
|
|
`scripts/openbao-attended-exec.py` supplies the WSL browser launcher and then
|
|
`exec`s into `warden access openbao-platform-admin-login --exec`, so Warden keeps
|
|
the envelope: it captures both streams, owns the temporary token helper and
|
|
self-revokes when the command returns. Use a receipt path that does not exist —
|
|
the writer opens it `O_EXCL` at mode 0600 and refuses to overwrite.
|
|
|
|
The runner prints nothing. Its entire output is the receipt.
|
|
|
|
**To settle the npm field alone**, run only the two questions that bear on it:
|
|
|
|
```sh
|
|
make openbao-open-questions SESSION_QUESTIONS=Q1,Q2 SESSION_RECEIPT=/tmp/<new-unique-name>.json
|
|
```
|
|
|
|
`--questions` defaults to all five. Q5 is the only step that reads the backup
|
|
lane, so naming a subset is also how to exclude it.
|
|
|
|
**Ambient-token guard.** The Make target clears these before invoking the
|
|
envelope; the runner independently refuses to start if `OPENBAO_TOKEN`,
|
|
`BAO_TOKEN` or `VAULT_TOKEN` is set in the environment. A standing token would
|
|
let these reads succeed without the attended login, producing a receipt that
|
|
looks attended and is not. Unset it first; the envelope supplies the identity.
|
|
`--allow-ambient-token` overrides, and then the receipt records
|
|
`attended_identity: false` — which is the honest label for that read, not a
|
|
formality.
|
|
|
|
## What the runner reads, and the one honest caveat
|
|
|
|
| Step | Call | Emitted |
|
|
| --- | --- | --- |
|
|
| Q1 | `secrets list`, then `kv metadata get` on the legacy path if the mount exists | mount present, path present, current version, created/updated time |
|
|
| Q2 | `kv get` on `platform/workloads/coulomb/whynot-design/npm-publish` | **sorted field names only**, plus KV version |
|
|
| Q3 | `policy read` for both KeyCape approval policies | present, matches-repo-source, sha256 of each normalized text |
|
|
| Q4 | `list auth/netkingdom/role`, then `read` each | role names, bound claims, groups/user claim, token policies, TTL |
|
|
| Q5 | `kv get` on `platform/workloads/railiance/backup/offsite-lane` | **sorted field names only**, plus KV version |
|
|
|
|
**The caveat, stated plainly:** Q2 and Q5 are data reads. Field names cannot be
|
|
obtained from KV v2 metadata, so the value transits the runner's memory. It is
|
|
never printed, never passed on argv, never written to disk, and never placed in
|
|
the receipt — `field_names()` returns key names and the values go out of scope
|
|
with the function. If that is not acceptable for the backup lane in particular,
|
|
drop Q5: it is context for RISK-F-0010, not a dependency of it.
|
|
|
|
For Q5 the receipt records `predecessor_material_recorded: false`, and no value,
|
|
fingerprint, length or shape of any predecessor credential is captured — which is
|
|
the constraint risk-nexus set.
|
|
|
|
## What each answer unblocks
|
|
|
|
- **Q1 + Q2 → secrets-engine and ops-warden.** Q2 is now the more urgent of the
|
|
two. ops-warden changed their catalog and playbook from `NPM_AUTH_TOKEN` to
|
|
`npm_token` on secrets-engine's statement, while this repository holds two dated
|
|
live receipts — including an attended founder fetch that exited zero — naming
|
|
`NPM_AUTH_TOKEN` as the field. If those receipts are right, that change points
|
|
the front door at a field that does not exist and the lane breaks on next use.
|
|
Q2 enumerates the real field names: `npm_token` only, `NPM_AUTH_TOKEN` only, or
|
|
both, and each outcome names exactly one side that has to move, as a reviewed
|
|
lane change carrying its own approval. If Q1 finds a populated legacy path, that
|
|
is a stale duplicate holding a real token and becomes its own disposition item —
|
|
do not delete it inside this session.
|
|
- **Q3 → internal.** Either the policies match the repo, and the activation's
|
|
`policy_applied: false` meant "no change needed", or they drift and that is a
|
|
finding to fix under RPF-WP-0035 before anyone treats the verifier lanes as
|
|
clean.
|
|
- **Q4 → CCR-2026-0019.** Existing bound claims show what the estate already uses
|
|
for operator lanes. This is **input, not confirmation** — the authorized group
|
|
claim is NetKingdom's and KeyCape's to state, and the receipt says so in-line so
|
|
a later reader cannot mistake the listing for an approval.
|
|
- **Q5 → risk-nexus.** Confirms the governed lane is populated with the expected
|
|
field names, supporting the fail-closed argument already made on that thread.
|
|
|
|
## Explicitly out of scope
|
|
|
|
- **The rapp-qonto KeyCape client rotation** (ops-warden `c1aafba4`). It is a
|
|
two-custodian CAS write with a service restart and needs its own reviewed
|
|
request first. Do not fold it into a read-only session because the operator
|
|
happens to be authenticated.
|
|
- **Predecessor share invalidation** for RPF-WP-0029-T02. That is a provider-side
|
|
action in the Nextcloud UI, not an OpenBao operation, and it needs the provider
|
|
owner. It can share the same sitting, but it is a separate step with a separate
|
|
receipt.
|
|
- **Any write, seed, rotation or policy apply.** The runner contains no mutating
|
|
verb; if a question turns out to need one, it comes back as a request.
|
|
- **Client-side reads for the approval clients.** CCR-2026-0019/0020 remain
|
|
in_flight; this session does not advance them beyond Q4's input.
|
|
|
|
## After the session
|
|
|
|
1. Store the receipt under `docs/evidence/` with the run date, values absent.
|
|
2. Reply to secrets-engine (Q1/Q2), risk-nexus (Q5) and, if Q3 drifts, record the
|
|
finding against RPF-WP-0035.
|
|
3. If Q1 finds a populated legacy path, open a disposition item for it rather
|
|
than acting on it in the moment.
|