Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 4515@bnt-lap001 Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
4 KiB
4 KiB
KeyCape owner acceptance: NK-WP-0049 isolated restore (issuer side)
Date: 2026-10-27 · Owner: key-cape · Scope: NK-WP-0049-T15 (KeyCape lane) and the issuer-side part of T18 Estate mode: builder (not production). Acceptance is proportional to that.
Verdict
Accept with named residuals for KeyCape's issuer-side restore. This is not an acceptance of consumer continuity (T18 remainder) or MFA (T19).
Evidence assessed
- NetKingdom verifier output
docs/evidence/2026-10-23-nk49-t18-keycape-attempt/verifier-output.json: receipt sha25616d72f7c…1651, verdictverified, no findings,claims_not_madelists consumer continuity, MFA and owner acceptance. - Native check source
net-kingdom/tools/bootstrap-recovery/isolated-keycape.py(reviewed T38 pin86f37add…), read against KeyCape source (src/cmd/keycape/main.go,src/internal/server/oidc/jwks.go,src/internal/server/policy/policy.go,src/internal/server/oidc/session.go). - No secret value, private receipt or live system was read for this review.
What KeyCape considers a correct restore, and coverage
| KeyCape state | Where it lives | Covered by the run |
|---|---|---|
| Signing key | key.pem → single JWKS key kid=key-1 (main.go:97) |
Yes. key.pem byte-equal to archive; live JWKS has exactly one RS256 key whose modulus/exponent equal the archived private key; a native exchange then verified against it. |
| Key rotation state | none: KeyCape constructs one key, no previous/retired keys | Nothing to restore. Single-key check is complete for this release. |
| Client registry | clients: list inside config.yaml |
All entries byte-equal (whole file). One confidential client_credentials client exercised natively. |
| Client secrets | KEYCAPE_* env from referenced Secrets |
Full KEYCAPE_* environment equal to archive; the tested client's secret accepted, unrelated secret and predecessor secret refused. |
| Policy / enrollment-mode store | /var/lib/keycape-policy/ (policy.json, mutable via policy API) |
Entire durable tree byte-equal, no extra files; service started, so policy.Open validated it against the restored clients. |
| Factor read token | /etc/keycape-factor/admin-token |
Byte-equal when present in archive; its use is T19 (passed 2026-10-24). |
| Issuer / TLS trust | issuer in config, /recovery-tls |
Original HTTPS issuer and pinned trust preserved; TLS reissued in isolated namespace, production cert unchanged. |
| Binary | admitted image digest | Image equals pinned digest; /keycape verifier hash checked. |
| Sessions, auth codes, token status | in memory (session.go) |
Not durable by design; loss on restore is expected (users sign in again; issued tokens stay verifiable because the key is preserved). |
Named residuals (do not block issuer-side acceptance)
- Only one client exercised natively. Other registry entries (browser/PKCE relying parties such as portal and Vergabe, other service clients) are proven byte-preserved, not natively exercised. Authorization-code + PKCE through the restored chain is the T18 consumer run (RPF-WP-0064 phase B / SECRETS-WP-0015-T04).
- Recovery point. The restore equals the selected archive. Any client or policy change made after that archive (registry additions, enrollment-mode changes via the policy API) would be lost; the operator must reconcile against the current repo-held client definitions after a real restore.
- Release coupling. The single-key check (
len(keys)==1,kid=key-1) is correct only for the current release. If KeyCape adds key rotation or a second key, the native check and this acceptance must be revisited. - Receipt custody. The receipt is held outside Git on the founder workstation (founder decision 2026-10-23); this acceptance relies on the verifier output's binding to its sha256.
Not accepted here
Consumer continuity (T18 remainder), caller admission (T21), credential dispositions (T22) and final acceptance (T03) stay with their owners.