feat: assert flex-auth ceiling keys are declared, not assumed

flex-auth fixed enrichment so registry facts beat caller-supplied ones
(FLEX-DEC-2026-012) and asked each consumer to confirm the ceiling and
allowlist keys are actually declared -- the fix wins only where the registry
HAS a value, and a manifest omitting max_ttl_hours hands that ceiling back
to the caller.

Confirmed, and made durable rather than read once.
scripts/check_flex_auth_manifest_coverage.py audits both ways a ceiling
gets handed back: an actor with no manifest resource at all (warden sign
names ssh-cert:actor/<name> whether or not the snapshot was rebuilt --
an honour-system step in SCOPE.md), and a resource missing one of the
seven keys. A null is treated as absent, because for enrichment it is.

Also asserts the property their exploitability assessment rested on and
nothing here held: ops-warden sends no resource.attributes. It was true
when they read it, secrets-engine sends them on every request, and it was
one refactor from silently stopping being true.

Current state: no gap. 4 actors, 4 resources, all seven keys declared.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1276224@bnt-lap001
Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3
This commit is contained in:
tegwick 2026-09-09 14:40:05 +02:00
parent 3a01b8f1b5
commit df48ee96e0
3 changed files with 288 additions and 0 deletions

View file

@ -307,3 +307,47 @@ def test_advisory_decision_is_recorded_and_does_not_block(tmp_path):
assert check_sign_policy(cfg, spec) == "decision:advisory"
assert spec.policy_zone == "unknown"
assert spec.policy_outcome == "audit_only"
def test_check_request_asserts_no_resource_attributes(tmp_path, monkeypatch):
"""The property FLEX-DEC-2026-012 turned on, asserted rather than assumed.
flex-auth's enrichment used to overlay registry facts additive-if-absent, so
a caller-supplied `resource.attributes` value won and the registry ceiling
never applied. Their exploitability assessment rested on ops-warden sending
no `resource.attributes` at all -- true when they read it, and nothing here
held it true. secrets-engine sends them on every request, so this is a
property of this code rather than of the protocol.
Ceilings must arrive from the registry. If a future change ever needs to send
an attribute, this test is the place that argument gets made.
"""
from warden import policy as policy_mod
pubkey = tmp_path / "id.pub"
pubkey.write_text("ssh-ed25519 AAAA test\n")
cfg = PolicyConfig(flex_auth_url="http://flex-auth.test")
seen = {}
class _Response:
status_code = 200
def raise_for_status(self):
return None
def json(self):
return {"effect": "allow", "id": "decision:49350f1064f674d7"}
def fake_post(url, json=None, headers=None, timeout=None):
seen["body"] = json
return _Response()
monkeypatch.setattr(policy_mod.httpx, "post", fake_post)
policy_mod.check_sign_policy(cfg, _spec(pubkey))
assert "attributes" not in seen["body"]["resource"]
# The requested TTL is a policy input (ttl_out_of_bounds is denied against
# the registry ceiling), so it must travel as context, never as a resource
# attribute that would be compared against itself.
assert seen["body"]["context"]["ttl_hours"] == 24
assert set(seen["body"]["resource"]) == {"id", "type", "system", "tenant"}