Commit graph

2 commits

Author SHA1 Message Date
d98323b2bb fix(secrets-engine): v2 adds the tenant rule v1 never had
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 41s
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
2026-09-06 20:38:45 +02:00
9e10d1cec1 Emit T03 replay fixtures from the published package
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 37s
Two real DecisionEnvelopes in examples/secrets-engine/replay/ for
secrets-engine to verify its digest join unchanged: a plain allow
(rotate, empty context) and the dual-control allow (destroy with a valid
approval-claim).

Both are included deliberately. input_claim_digests.context appears only
when the request carries a non-empty context, so a consumer asserting the
field is always present would pass on destroy and fail on rotate. One
fixture would have hidden that.

request_digest, policy_package_digest, registry_snapshot_digest and the
context claim digest are verified identical across two runs and are the
fields to pin. id, decision_time and the lifetime bounds move with the
clock; the README says so rather than leaving a consumer to discover it
by flake. lifetime.ttl is 15m from the package allow_ttl.

Emitted from flex-auth/local in standalone mode, not from a cluster pin.
T03 is progress, not done -- it closes when secrets-engine confirms their
validator accepts the records unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTbVXpEiXA7mNJVpDnEPcB

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 412054@bnt-lap001
Assistant-Session: 3968fae1-8d59-4209-9bd6-c22594b8ab19
2026-09-06 08:14:14 +02:00