fix(secrets-engine): v2 adds the tenant rule v1 never had
secrets-engine.catalog-lane.lifecycle v1 contained no reference to input.tenant — not in well_formed, not in the denial ladder, not in a test. A rotate on lane:glas-primary under tenant:coulomb returned allow against the deployed package (decision:066e629bbf0c0924). Found while answering glas-harness's tenant-alignment request, which had asked for wrong-tenant denial evidence. There was none to return. Three covers failed the same way: every one of the 29 fixtures carried tenant:platform, so the suite could not report on the field; T02's own gate named "wrong-tenant deny" and was recorded done unmet; and the engine hashes tenant into request_digest but never compares it. Four other published packages carry the branch — this one was the outlier. v2 adds wrong_tenant above wrong_system, three Rego tests and three fixtures (28/28, 32/32). The absent-tenant test caught a second defect in the first draft: a bare input.tenant != comparison is undefined on a missing key, so the branch dropped and the ladder reported the wrong rung. request_tenant := object.get(input, "tenant", "") fixes it. v2 supersedes rather than amends v1 because the defect failed open: a consumer pinned to _VERSION=v1 would keep receiving allows with no signal the rule beneath the version string had changed. The earlier dual-control correction stayed at v1 because it denied everything. Replay envelopes regenerated at v2; both request_digest values are byte-identical, so secrets-engine's digest join needs no re-pinning. The sweep this prompted found tenant-engine unscoped on tenant as well — deployed, and verified allowing tenant:coulomb. Not the same fix: its request tenant names the target rather than the caller, so a constant would break it. Recorded and carried by FLEX-WP-0022 rather than patched unilaterally. FLEX-WP-0021 closes at T05; the pin still serves v1 until a redeploy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aQMM1dPXaPiXVn6DwwtLd Assistant: claude-code Assistant-Model: opus Assistant-Process: 715613@bnt-lap001 Assistant-Session: fabd95c1-4c9e-4080-8849-8707ae025f80
This commit is contained in:
parent
a81697a589
commit
d98323b2bb
12 changed files with 690 additions and 28 deletions
|
|
@ -7,6 +7,7 @@ to verify its digest join (`627810b`) unchanged. `FLEX-WP-0021-T03`.
|
|||
| --- | --- |
|
||||
| `decision_rotate.json` | `../check_request_allow_rotate.json` — plain allow, empty context |
|
||||
| `decision_destroy_dual_control.json` | `../check_request_allow_destroy_dual_control.json` — dual control, valid approval-claim |
|
||||
| `decision_wrong_tenant_deny.json` | `../check_request_deny_wrong_tenant.json` — foreign tenant, denied `wrong_tenant` |
|
||||
|
||||
Regenerate either with:
|
||||
|
||||
|
|
@ -24,12 +25,26 @@ go run ./cmd/flex-auth check \
|
|||
| Field | `rotate` | `destroy` |
|
||||
| --- | --- | --- |
|
||||
| `binding.request_digest` | `sha256:de67324f…4345` | `sha256:c749ee2…091a` |
|
||||
| `provenance.policy_package_digest` | `sha256:fe0070b7…bd8c` | same |
|
||||
| `provenance.policy_package_digest` | `sha256:bd11c5fe…c643` | same |
|
||||
| `provenance.registry_snapshot_digest` | `sha256:f5a309bc…40bb` | same |
|
||||
| `provenance.input_claim_digests.context` | absent (empty context) | `sha256:8b73d29…2800` |
|
||||
|
||||
Verified identical across two runs.
|
||||
|
||||
**Regenerated at `v2`, 2026-09-06.** The package gained the tenant rule it had
|
||||
been missing, so `provenance.policy_version` is now `v2` and
|
||||
`policy_package_digest` moved from `sha256:fe0070b7…bd8c` to
|
||||
`sha256:bd11c5fe…c643`. **Both `request_digest` values are unchanged** — the
|
||||
canonical digest covers tenant/subject/action/resource/context and not the
|
||||
package, so a consumer that pinned the digest join has nothing to re-pin. Only
|
||||
the two provenance fields moved, and they moved because the policy did.
|
||||
|
||||
`decision_wrong_tenant_deny.json` is the denial evidence for the tenant
|
||||
question: the same `rotate` request as `decision_rotate.json` with
|
||||
`tenant: tenant:coulomb` substituted. Its `request_digest` differs from the
|
||||
allow's, which is the replay identity working — the tenant is hashed material,
|
||||
so the two requests are not the same request. A deny carries no `lifetime`.
|
||||
|
||||
| `binding.approval_binding_digest` | absent (no claim) | `sha256:fa07bec…cd56` |
|
||||
|
||||
That last row is the value an approval's `pdp_digest` must equal — never
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue