ops-mason/docs/keycape-client-custody-attended-verification.md
tegwick 969fe218df Prepare independently reviewed native custody capability verification
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 14:16:58 +02:00

168 lines
10 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.

# MASON-WP-0009-T05: founder-run native capability proof
Preparation only. No credential was read, native session started, TokenRequest
issued, policy widened, KV GET performed, or route activated during preparation.
The existing T05 task stays in progress. Formal ops-mason owner source review,
founder attendance approval for the exact staged digest, and live native receipts
remain necessary. An `--approval-reference` argument records the founder's prior
disposition; it cannot approve itself or establish runtime isolation.
## Exact reuse and authority
The existing verifier `scripts/verify-fluid-telegram-lane.sh` demonstrates attended
OIDC and value-free capabilities, but accepts every failed login as a nonmember
refusal. It is not reused as proof. secrets-engine's
`OpenBaoOidcClientSecretSource` demonstrates `bao login -no-store -format=json`,
but its next operation reads CLIENT_SECRET, so that provider is not invoked here.
Existing `scripts/bao-session.sh grant MASON-WP-0009-T05` provides query authority:
45m `ops-mason-survey`, founder minting, explicit data denies, no policy/role/token
write. The grant may also carry `default`; all other token/identity policies are
refused. No new policy or credential owner is proposed.
Source inspection found neither CCR-0033's ESO reader nor CCR-0034's operator
reader grants `sys/capabilities-self` (both omit default). Calling capabilities
under their own tokens would prove endpoint denial, not the required path matrix.
The verifier uses the survey grant's existing `sys/capabilities` to query the
native lane token in memory. The lane token never enters argv, environment,
receipts or a token helper. All HTTP calls use a fixed loopback endpoint, reject
redirects/proxies and allow only login, self lookup, capability query and self
revocation endpoints. No secret or metadata GET exists in the HTTP interface.
Credential routing was read first: `WARDEN_MEMORY=0 warden route show
openbao-structure-survey --json`. That entry routes to ops-mason and is not a
credential vending surface. The initial default route lookup printed routing
metadata but failed to append its memory event outside this sandbox; no escalation
was requested. There is no active operator route in current ops-warden source;
the previously reported draft proposal must be accepted there separately.
## Concrete attended proposal
Proposal ID: `MASON-WP-0009-T05-native-capability-proof-20261011`.
Target: existing OpenBao on the railiance01 loopback tunnel and, for ESO only,
railiance01's `external-secrets/external-secrets` ServiceAccount TokenRequest.
Expected result: two exact native read-only matrices, confirmed lane/query
self-revokes, and a separately accepted nonmember refusal. Estimated software
cost EUR 0; bounded founder browser/session time around 10–20 minutes. Risk:
short-lived read-capable session tokens exist in founder process memory, and ESO
TokenRequest briefly mints a 10m JWT for the existing ServiceAccount. No secret
value is requested. No policy or deployment changes occur. Actual runtime
isolation remains unverified; exact source review and founder attendance are
procedural gates, not an autopilot grant.
After ops-mason accepts the source and the supervisor records the exact digest
and scoped founder go, the founder runs from their terminal. For each mode,
obtain a fresh existing survey grant through the established session helper.
Do not hand an agent an operator token or inspect either session file in chat.
Use a new output path each time and a private browser session for the chosen human.
The verifier requires TTY attendance, rejects ambient BAO_TOKEN/VAULT_TOKEN and
namespaces, and refuses a source digest different from the reviewed digest.
```bash
python3 scripts/verify-keycape-client-custody.py operator \
--attended --approval-reference '<recorded founder disposition>' \
--approved-source-sha256 '<reviewed script digest>' \
--survey-token-fd 3 --receipt '<new absolute 0600 receipt path>' \
3< "$HOME/.claude-bao-token"
```
Repeat with `eso` and then `nonmember`, fresh grant and receipt for each. Opening
the existing grant FD is performed only in the founder terminal. The verifier
closes it and self-revokes the query token. Existing `bao-session.sh revoke` is
then used by the founder to remove the stale grant file; its printed success is
not accepted as revocation evidence because that helper suppresses revoke errors.
The verifier's native revoke receipt is the relevant evidence.
For ESO the runner first captures node metadata via fixed `ssh -oBatchMode=yes
railiance01 'kubectl get nodes -o json'` and refuses any estate other than one node
with address `92.205.62.239`. It then captures only the exact audience `openbao`,
10m ServiceAccount TokenRequest JWT in memory and logs in at
`auth/kubernetes/login` as `external-secrets-keycape-secrets-engine-openbao`.
No Secret object, pod or controller token is read. The independently minted lane
token is revoked; the running ESO controller's token is never touched. If the
node metadata does not expose the documented IP, stop for platform owner source
review of the estate pin; do not loosen the check at runtime.
## Receipt acceptance and remaining proof gates
Schema `mason-client-custody-native/v1` includes exact script digest, proposal
reference, mode, timestamp, accessor SHA256, matrix and cleanup booleans. Files
are reserved with O_EXCL/O_NOFOLLOW and mode 0600. Raw CLI/HTTP errors and auth
responses are never copied into receipts. Capability queries must return every
named path; missing rows, unexpected default/identity policies, wrong role,
wrong ESO namespace/name, TTL over 5m/15m, any data write or parent/sibling/metadata
capability fail closed. `capability_matrix_observed` means observations passed;
`owner_acceptance: pending` is retained even when exit status is zero.
Operator and ESO acceptance requires ops-mason's actual owner review against the
existing CCRs/source receipts, native role/policy/TTL observations, exact read-only
matrix and **both** lane/query revocation confirmations. Operator role/claims
remain grounded in the existing approved CCR-0034 binding, not a CLI role label
chosen by the human. ESO metadata must show the fixed service account and role.
Nonmember never auto-passes. A semantic groups mismatch from the OIDC callback is
only `group_refusal_candidate`; transport failure, cancellation, HTTP 403 alone,
wrong issuer, malformed JSON, or MFA failure is `unproved_login_failure`. An
unexpected successful nonmember login is revoked and fails the run.
Before ops-mason accepts check 3, key-cape supplies a value-free owner-authenticated
identity record proving the **same attempted subject** is human, outside
net-kingdom-admins and aal2, with validated issuer/signature/expiry; platform supplies
a correlated native OpenBao callback refusal specifically for groups. The records
must bind the same attempt using an owner-verified request/flow correlation digest,
not merely nearby timestamps, a user-selected `nonmember` mode or decoded JWT
claims. Include owner record ids/digests, observed time, subject fingerprint,
issuer validation boolean, `group_member=false`, `aal2=true`, callback endpoint,
refusal class and no token issued. The identity/flow digest must be produced and
verified inside the owners' attended/audit surface; the runner does not possess
the upstream OIDC JWT and cannot manufacture this evidence. If owners cannot
produce this correlation, check 3 stays unproved. Never publish raw JWTs, callback
URLs/codes/states, cookies, session ids or authentication errors.
This is separate from KEY-WP-0039 human/coding-agent JWT boundary evidence and
secrets-engine's service identity denial matrix. Those do not prove this reader's
nonmember binding. The existing successful service-jwt exchange and self-revokes
remain accepted prior evidence; do not discard or regenerate them merely to
complete these different gates. ops-warden may activate the pointer only after
all T05 required owner-native receipts are actually accepted.
## Cleanup and failure recovery
On every obtained lane token, including policy mismatch/unexpected nonmember
success, attempt lane revoke-self, then query revoke-self. A failed revoke changes
result to unproved. A query token that fails its identity check is not trusted for
further calls; record no cleanup confirmation and use the established founder
revocation path. Callback timeout/malformed native auth may leave a token whose
handle was not recovered: no cleanup success is claimed; the role's 5m/15m TTL
bounds it and the platform owner decides recovery. No policy is widened to retry.
The Kubernetes TokenRequest JWT cannot be individually revoked by this procedure;
it expires after 10m and is never persisted. Do not delete the shared ServiceAccount
or revoke the controller's token as cleanup. No KV rollback exists because no KV
request occurred. Session cleanup is the rollback; any unconfirmed cleanup is
explicit open evidence and blocks acceptance until owner resolution.
## Corrected source revision — 2026-10-11
The prior immutable stage is retained at `/tmp/mason-jwt-capability-next-stage`
(manifest e5d4781c6280c3bc8a3930c63cfb563f59752465720d6c2c3e95eb0551f8e949).
Independent source review requested changes: its HTTP202/text response could
falsely confirm revocation and missing native accessors could hash the empty
string. This revised source rejects every unsupported status/content/body/error
shape. Lookup/capability/Kubernetes-login require HTTP200 application/json and
endpoint-specific object data/auth, no `errors` member, with bounded headers/body.
Revoke succeeds only on HTTP204 with an empty body. Error envelopes never imply
cleanup; a recovered token from a mixed login envelope is retained solely for
finally cleanup. Nonempty bounded typed actual accessors are required before
fingerprinting; empty/malformed query accessors still revoke a scope-validated
query token, and empty/malformed lane accessors still revoke recovered lane tokens.
TTL is a positive integer within the declared bound, with booleans refused.
CLI stdout/stderr are captured while running with a 64KiB limit per stream;
excess or timeout kills the local process group and leaves native outcome
unproved. No unlimited capture is described as bounded. The exact approved
source digest is checked before receipt/token/native IO. 29 offline tests cover
these corrections, actual relative redirect refusal with hostile proxy settings,
synthetic oversized subprocess output, valid native204 cleanup versus bad202,
missing-accessor cleanup, and mixed-error session recovery. Source corrections
are not a new native success, owner acceptance, founder go or adoption proof.