NK-WP-0056-T19 done: approval exercise receipt (16 checks against KeyCape 9e082dca, approval-engine f41a9ca)
All checks were successful
All checks were successful
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 523928@bnt-lap001 Assistant-Session: 6c737afb-9c14-4152-9f02-477214b9c350
This commit is contained in:
parent
e1ef4bf6b1
commit
66dc397193
4 changed files with 146 additions and 3 deletions
|
|
@ -0,0 +1,68 @@
|
|||
# NK-WP-0056-T19: executor approval exercise against the real services (2026-11-03)
|
||||
|
||||
Agent-run, local only, synthetic keys, everything removed afterwards:
|
||||
`python3 bounded-executor/exercise_approval_identity.py --receipt FILE`. It follows secrets-engine's
|
||||
`tools/exercise_approval_identity.py`.
|
||||
|
||||
**Setup:**
|
||||
|
||||
- **KeyCape:** a disposable container on the pinned image `9e082dca` (`sso-mfa/k8s/keycape/deployment.yaml`). It is
|
||||
configured with two clients:
|
||||
- the `client-from-case` output for case `nk-bind-executor-approval`;
|
||||
- key-cape's example `approval-engine-operator`, which acts as the revoker and as a service that tries to approve.
|
||||
- **approval-engine:** source `f41a9ca`, in-process, with its `JWTAuthenticator` against KeyCape's JWKS.
|
||||
- **Executor:** the real code. That covers `cli.run_request`, `KeyCapeClientCredentials`, `ApprovalEngine`, and
|
||||
`executor.Run.preflight` and `consume`. Only the OpenBao lane is replaced: the generated client secret is handed to
|
||||
the client directly. The T18 tests cover that lane.
|
||||
|
||||
Receipt: [`receipt.json`](receipt.json). It is value-free; the harness asserts that no generated secret appears in it.
|
||||
It records the image, the source commits, the executed files' hashes and the case hash. The commits are
|
||||
`issuer_contract_commit` (key-cape), approval-engine, and net-kingdom `e1ef4bf` (executor code from `625ad65`).
|
||||
|
||||
| Check | Result |
|
||||
| --- | --- |
|
||||
| The request phase creates the approval as `service:nk-bind-executor` through a verified KeyCape JWT | passed |
|
||||
| Create with another actor | 403 |
|
||||
| Preflight before approval | refused, `approval_status` |
|
||||
| A service (`approval-engine-operator`) approves a human-control approval | 403, no entry |
|
||||
| A human aal2 entry approves | `approved` |
|
||||
| The executor's preflight accepts KeyCape's aal2 entry | passed (after the fix below) |
|
||||
| Consume | `consumed`; the read-back shows the admission digest and this run's id |
|
||||
| Retry with the same digest | idempotent |
|
||||
| A second run on the consumed approval | refused before consume |
|
||||
| A different digest | 409; the consumption is unchanged |
|
||||
| A changed admission | refused, `approval_binding` |
|
||||
| Revoke before consume | refused by the executor and by approval-engine (409). The executor's own token cannot revoke (403) |
|
||||
| A human aal1 entry | accepted by approval-engine (no floor declared), refused by the executor (`approval_entry`) |
|
||||
| The executor client asks for `approval:approve` | KeyCape refuses with `invalid_profile_usage` |
|
||||
| A token for another scope | 403 on create and on consume |
|
||||
| A wrong client secret | refused at the token endpoint |
|
||||
|
||||
**Limitations:**
|
||||
|
||||
- approval-engine runs from source, not from the deployed image.
|
||||
- The founder's entry is seeded through approval-engine's store. It uses the exact assurance object KeyCape mints and
|
||||
the API persists, but there is no human PKCE login.
|
||||
- There is no informed-decision presentation; the T21 tests cover it.
|
||||
- Plain HTTP on loopback.
|
||||
|
||||
## Finding that changed the executor
|
||||
|
||||
**Every real founder approval would have been refused.** approval-engine stores an entry's `assurance` as the
|
||||
verified token's claim, serialized as JSON: `{"at", "level", "methods", "mfa", "source"}`. KeyCape mints that object
|
||||
in `token.go` `assuranceClaim`, and approval-engine stores it in `api.py`. The executor compared it with the bare
|
||||
string `"aal2"`, which the fakes supplied. Against the real store, preflight refused with `approval_entry`.
|
||||
|
||||
The fix (`625ad65`): `executor.entry_is_aal2` parses the object and requires level `aal2` or `aal3` with `mfa: true`.
|
||||
Anything else fails closed, including a bare `"aal2"`. The fakes use the real shape, and a unit test covers the
|
||||
refusals.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **The pinned executor image needs rebuilding.** The image pinned in the rapp by T18 (`c67cb044`, `b90951a`) has the
|
||||
defect. It must be rebuilt from `625ad65` or later and re-pinned before T06.
|
||||
- **Open for the founder.** approval-engine accepts an aal1 human entry unless the approval declares
|
||||
`required_assurance` (APPROVAL-WP-0007-T01). Today the executor is the only aal2 guard. The request phase could
|
||||
declare `required_assurance: {"level": "aal2"}` so approval-engine also enforces the floor at approve and consume
|
||||
time. That needs the deployed approval-engine to be at schema v7 or later, and a check that informed-decision's
|
||||
present_existing accepts the field.
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"schema": "netkingdom.bounded-executor.approval-exercise/v1",
|
||||
"task": "NK-WP-0056-T19",
|
||||
"target": "disposable local processes; synthetic keys and credentials",
|
||||
"started_at": "2026-11-03T22:15:58+00:00",
|
||||
"keycape_image": "forgejo.coulomb.social/coulomb/key-cape@sha256:9e082dca78f65a4af3e92d3de3705b99e49ca9b298d9562ad0d37511c4306f8d",
|
||||
"issuer_contract_commit": "82b471fc368a952fa4bbe83e8c8cfcfe56c443b9",
|
||||
"approval_engine_commit": "f41a9ca82ce0e491e7846c5e2ae33fcbd3bde667",
|
||||
"net_kingdom_commit": "e1ef4bf6b19b211c62f4ff1ff5c430af78d64250",
|
||||
"executor_source_sha256": {
|
||||
"cli.py": "b6cb53a7c71a06c61daf02ff1740d065cd754b1b5d2640571cce2b3f44ad8ff2",
|
||||
"clients.py": "f0ce580f1beba91b7154051a35c088205de01f26d63a119a23bd926fe1c772cb",
|
||||
"executor.py": "3da69db9cdd0ba7241246204dd92a57a84779f078ea3cd6d2705f3a8cc11b544"
|
||||
},
|
||||
"case_sha256": "32ba48b4131e22c993bbf8e705bd4f371a0e11222b069279cd196f2b880003c5",
|
||||
"keycape_client": {
|
||||
"clientId": "nk-bind-executor-approval",
|
||||
"audience": "approval-engine",
|
||||
"allowedScopes": [
|
||||
"approval:create",
|
||||
"approval:read",
|
||||
"approval:consume"
|
||||
],
|
||||
"grantTypes": [
|
||||
"client_credentials"
|
||||
],
|
||||
"serviceSubject": "service:nk-bind-executor",
|
||||
"tenant": "tenant:platform",
|
||||
"tokenLifetime": "15m"
|
||||
},
|
||||
"limitations": [
|
||||
"approval-engine runs from source in-process, not the deployed image",
|
||||
"the founder's aal2 entry is seeded through approval-engine's store; no human PKCE login",
|
||||
"the client secret is handed to the client directly; the OpenBao lane is covered by the T18 tests",
|
||||
"no informed-decision presentation (T21 tests cover the memo against its real validator)",
|
||||
"plain HTTP on loopback; production uses KeyCape's public HTTPS issuer"
|
||||
],
|
||||
"checks": {
|
||||
"request_phase_creates_as_executor_via_verified_keycape_jwt": true,
|
||||
"create_with_another_actor_refused": true,
|
||||
"preflight_refuses_unapproved": true,
|
||||
"service_approve_of_human_control_refused": true,
|
||||
"human_aal2_entry_approves": true,
|
||||
"executor_preflight_accepts_keycape_aal2_entry": true,
|
||||
"executor_consumes_once_and_confirms_by_read_back": true,
|
||||
"same_digest_retry_idempotent": true,
|
||||
"second_run_on_consumed_approval_refused_before_consume": true,
|
||||
"different_digest_refused": true,
|
||||
"changed_admission_refused_by_binding_digest": true,
|
||||
"revoke_before_consume_refused_by_executor_and_engine": true,
|
||||
"aal1_human_entry_refused_by_executor": true,
|
||||
"executor_client_refused_approve_scope_by_issuer": true,
|
||||
"token_for_another_scope_refused_by_engine": true,
|
||||
"wrong_client_secret_refused": true
|
||||
},
|
||||
"status": "passed",
|
||||
"cleanup_complete": true,
|
||||
"finished_at": "2026-11-03T22:16:00+00:00"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue