Skip to content

authz: make issued policy and request objects side-effect immutable #168

Description

@seonghobae

Problem

PR #65 bound purpose-based authorization to creation-time _PolicySnapshot / _RequestSnapshot state, so post-construction object.__setattr__ rewrites no longer widen authorization. A narrower integrity defect remained in the public frozen dataclasses: calling the generated class __init__ again wrote fields before __post_init__ reached the already initialized guard. The second initialization raised but could leave observable live state changed; evaluation still failed closed, so this was a side-effect-integrity/audit-value defect rather than an authorization bypass.

Fresh owner review then found a second lifecycle defect after that repair: an exact PurposeBoundAccessPolicy or PurposeBoundAccessRequest could be allocated with object.__new__, populated with otherwise valid exact fields via low-level writes, and passed directly to public __post_init__. The old hook validated and registered that object even though the governed constructor had never run. Because the evaluator accepts exact runtime classes and issued snapshots, that path could mint authorization authority outside the constructor provenance contract.

Test-first repair

  • RED 79824637ac1e02e596622e85923b4884babf122d covers policy/request second-constructor calls.
  • Production fix 2318ff8b90c5029532b94da30396b7330405d953 gives the two issued input dataclasses explicit keyword-only constructors that reject already-issued instances before slot writes.
  • Test-only successor e92d2ac44db0a38a126d6be3e48eec95b431bb5a fixes the reinitialization exception oracle after CodeRabbit identified that the pre-write lifecycle guard correctly raises TypeError before downstream value validation.
  • Constructor-provenance RED 0fef3f8e2df49ad008034d730e125d64c240eb92 forges exact policy/request objects without constructors and proves direct __post_init__ must not register them.
  • Minimal fix dc945885dee354c16ca68a2fece18c0d58973faf binds policy/request snapshot issuance to constructor-scoped lifecycle state cleared in finally.

CodeRabbit reviewed exact dc945885… against protected develop@ef1b143… and reported no static issue in that policy/request constructor-provenance scope. This remains predecessor static evidence only after later #65 commits.

Remaining acceptance evidence

Keep this issue open. #65 has advanced non-force to b75fb41243ec71076fb2dcbd360ab55150398515 on protected base develop@ef1b143368cb6249c9520ca8cae10ebe844a5aa1 because fresh owner review found the distinct final-decision issuance gap tracked in #169. #168's repair remains in ancestry unchanged, but every hosted/review result from dc945885… is predecessor evidence after the new material commits. Close #168 only after the current unchanged #65 head has fresh terminal package/People/security evidence and ordinary protected integration is otherwise satisfied.

#169 is not a replacement for this finding: #168 owns policy/request constructor and post-construction integrity; #169 owns proof that the final AuthorizationDecision was minted by governed evaluation rather than consumer construction. Do not copy either repair into #163/#165; #65 remains the canonical authorization-evidence owner.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions