Skip to content

IamGuard denials produce status=VERIFIED at the diagnostic layer (allowed=False) — and mint valid VERIFIED tokens for denials; VC wrapper gate is the only protection #58

Description

Summary

An explicit IAM denial produces status=VERIFIED at the raw-diagnostic layer. IamGuard.verify_access returns verified=True, allowed=False for a denied action (verified = "the Z3 evaluation completed", allowed = the access answer), and to_diagnostic maps that to InfraDiagnosticResult.verified(...) — so a denial carries is_verified=True and is_authoritative=True, the documented authority bit. mint_diagnostic_attestation will then mint a valid VERIFIED token for a denial.

Found in the 2026-08-24 MAIN-vs-PR54 audit — CONFIRMED BY EXECUTION (present on MAIN and PR54 alike; unrelated to #47, which is why it survived).

Reproduction (CONFIRMED BY EXECUTION)

pol = {"Statement": [{"Effect": "Allow", "Action": "*", "Resource": "*"},
                     {"Effect": "Deny",  "Action": "s3:*", "Resource": "*"}]}
r    = ig.verify_access(pol, "s3:GetObject", "arn:x")      # verified=True, allowed=False (correct Z3 answer)
diag = IamGuard.to_diagnostic(r)                            # status=VERIFIED, fields.allowed=False
mint_diagnostic_attestation(diag, engine="iam", query="q")  # .is_issued == True — VERIFIED token for a denial

What saves it today (and why it's still a finding)

to_verification_context has a provenance gate — only allowed is True reaches ADMIT; the denial path correctly produces DENY (verified). So the VC-document consumer is protected. But:

  • Direct consumers of the diagnostic (the layer other ecosystem repos treat as the authority bit) see VERIFIED for a denial.
  • The attestation layer happily certifies it: a downstream checking token.verified and claims.result.status == "VERIFIED" — both true — would admit unless it also parses the nested evidence for allowed=false, which nothing in enforce_trust_decision's binding checks does.

Remediation (pick one, deliberately)

  1. Denials map to BLOCKED (or a distinct DENIED outcome) at the diagnostic layer — cleanest for the ecosystem's "VERIFIED means the claim was proven" convention.
  2. Keep the current semantics but write the convention down ("status proves the evaluation; the outcome lives in developer_fields.allowed") and add allowed to the attestation claims + enforce_trust_decision binding so tokens carry it machine-readably.

Option 2 is smaller; option 1 matches qwed-verification's status contract.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2P2 — hardening / semantic correctnessbugSomething isn't workingguardIndividual guard implementationvc-v1.0Verification Context v1.0 adoption

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions