Repository navigation
security(keyverse): validate exact operational identity across the adapter boundary #296
Description
Finding
The Keyverse adapter has one shared operational-identity integrity invariant but two canonical writer lanes.
- fix(keyverse): fail closed without authenticated identity authority #295 owns the identity-subject candidate/binding ACL boundary.
- fix(authz): protect purpose-bound runtime integrity #65 owns purpose-bound authorization runtime integrity.
Do not duplicate mutable source between them. Keyverse remains identity/authentication authority; Orgmetra remains HR policy/evidence authority.
#295 — binding owner
Current exact head: 086fe88607b1e30f72633b5222fbcaf8f846e9ca on protected develop@eb9757f8649aaad026a9865508d9aad50c1a7a4f, open / Draft / mechanically mergeable.
The binding lane now rejects non-UUID and reserved/forged UUID identities, detaches caller-owned UUID aliases, requires exact canonical issuer/subject text, treats optional claim metadata as inert exact built-in names, rejects credential-shaped fields before trust classification, and exposes only non-authorizing ExternalIdentityBindingCandidate data.
Fresh audit also found that the preceding frozen-slots dataclass could be rewritten after successful validation via low-level object.__setattr__. Test-first 634daefaad125b6de49bac4e0905dec1f822ccf3 requires post-validation replacement to fail; its hosted jobs were cancelled by successor publication before execution, so it is not claimed as hosted RED. Ordinary successor 086fe886... uses tuple-backed structurally immutable retained storage while preserving the public keyword constructor and field properties.
Structural immutability is not trust provenance. Low-level construction can still manufacture a Python value, so any consequential consumer must reconstruct/revalidate exact owner evidence rather than infer authentication from object type. Positive durable binding remains unavailable until Keyverse publishes the released contract tracked in Keyverse#155 / Orgmetra#297.
Fresh Foundation 34540431536, SAST 34540431586, Security 34540431607, and CodeQL 34540431529 generations are currently queued for exact 086fe886...; Foundation job 103081629173 has runner_id: 0, empty runner identity and no steps. Queue-health handoff is .github#712 comment 5626634340. No predecessor result is promoted to this head.
#65 — authorization owner
#65 remains exact b7f2b730b8b231aa28e485c655fd8cdf18007d46, open / Draft / mechanically mergeable, on the same protected base. It owns authorization.py, policy/request detachment and revalidation, coherent AuthorizationDecision evidence, and durable-consumer authorization checks.
Its prerequisite order remains #63 shared HRIS-kernel protected integration → #64 People mutation protected integration → #65 non-force protected adoption/reconciliation. #295 must not absorb this source simply to make this issue appear complete.
Acceptance
- fix(keyverse): fail closed without authenticated identity authority #295 exact-current-head preserves exact operational UUID, exact/canonical text, inert claim-name, credential-rejection and post-validation structural-integrity invariants at repository coverage targets.
- fix(authz): protect purpose-bound runtime integrity #65 exact-current-head preserves equivalent or stronger operational-identity integrity at authorization construction, evaluation and consequential consumers.
- Future binding consumers revalidate/reconstruct released trust evidence; locally forgeable language objects are never authorization/authentication authority.
- Applicable exact-head repository/security/review gates pass without weakening manifests, workflows or owner boundaries.
- Both canonical lanes reach normal protected integration before this issue is completed.
No force-push, destructive rebase, self/model approval, routine admin bypass, gate weakening, mutable-owner source copy, predecessor-verdict transfer, empty retrigger commit, or simple Close is authorized.
Activity
[-]security(keyverse): validate exact operational identity before persisting a subject binding[/-][+]security(keyverse): validate exact operational identity across the adapter boundary[/+]seonghobae commentedon Sep 10, 2026
Fresh owner split refinement: Issue #297 now owns a separate authentication authority/provenance gap above #296's operational-identity integrity.
#295 test-first head
97e12be2f3131e9ecbdeec99218c12f45705385bproduced a real Foundation RED (34513085296/102991763652) after tests stopped accepting raw caller issuer/subject as durable binding authority. Current #295 headd7b7f531e216027a99efe31e36dc1aab60fe23afmakes raw inputs onlyExternalIdentityBindingCandidatedata and makesbind_identity_subject(...)fail closed until an immutable released/versioned Keyverse subject-assertion contract exists; Foundation34513172553/102992051252is GREEN.This does not replace #296: exact UUID/text integrity and alias detachment still belong here, split across #295 binding and #65 authorization. #297 additionally prevents syntactically valid values from laundering caller assertion into authentication truth. Both issues remain open; no local trusted boolean/receipt or mutable Keyverse source copy is an acceptable completion.
seonghobae commentedon Sep 10, 2026
Current owner reconciliation: #295 binding/ACL lane is now exact
d15a741c10685208b124477e9d7820cdf17339e8, Draft and mechanically mergeable on protecteddevelop@eb9757f8649aaad026a9865508d9aad50c1a7a4f. Its current diff has five files after the fail-closed public boundary, CHANGELOG reconciliation, and deterministic manifest reseal; do not treat the earlier four-file snapshot as current authority.Single-writer split remains unchanged: #295 owns Keyverse identity-subject binding/ACL integrity; #65 owns purpose-bound authorization internals/runtime-integrity. #295 must not reintroduce
authorization.pyor its tests, and #65 must not absorb mutable #295 binding source. The manifest collateral RED introduced atcf9d6d1...was repaired byd15a741...; exact-head Foundation34515073682/102998336570is GREEN.#296 remains open until both owner lanes integrate normally through protected
developand downstream durable consumers reacquire exact protected-head evidence. Issue #297 separately tracks the absent immutable Keyverse subject-assertion release required for any future positive durable-binding path.seonghobae commentedon Sep 10, 2026
State-only currentization after the prior owner note: #295 was subsequently marked Ready for Review without changing head/base and remains exact
d15a741c10685208b124477e9d7820cdf17339e8on protecteddevelop@eb9757f8649aaad026a9865508d9aad50c1a7a4f. This does not alter the single-writer split or completion boundary. Repository-owned Foundation/SAST/Security evidence is current-head GREEN, but CodeQL is non-passing on the central producer/consumer settlement defect, Noema/Strix remain independently pending at the last read, and there is still no qualifying independentAPPROVED. #296 therefore remains open; no authorization-source overlap or mutable sibling adoption is authorized.seonghobae commentedon Sep 10, 2026
Fresh #295 binding-owner extension on top of
d15a741c10685208b124477e9d7820cdf17339e8found the same runtime-integrity class in optional claim-name screening.Protected/current binding code used
claims = extra_claims or {}and thenname.lower()over caller-provided keys without first requiring an exact built-in dict and exact built-in string keys. A dict subtype can therefore execute caller-defined truthiness before the trust gate; astrsubtype key can execute caller-definedlower(), and a non-text key leaks an incidentalAttributeErrorrather than the bounded validation contract. This does not claim sandboxing against arbitrary code already running in-process; it removes behavior-bearing values from this specific trust-boundary validation path.Test-first
85c08ac7e991877171d44dd523aaad585c599259adds regressions for executable dict/key subtypes and malformed non-text claim names. Its hosted workflows were superseded/cancelled before a test result, so it is not claimed as hosted RED; the predecessor source behavior itself reproduces the unbounded calls. Ordinary-forward source repaira3fa3470b6b4b489889b574afcc1f672a7fdedccadds_validate_extra_claim_names:Noneor exact built-indictonly, exact built-in string keys only, then credential-name comparison; claim values remain uninterpreted/unretained. Existing credential-name rejection semantics remain intact.#295 has been returned to Draft while exact-head hosted evidence is reacquired. #65 remains the authorization writer; no authorization source moved back into #295.
seonghobae commentedon Sep 10, 2026
Exact-current-head GREEN recovered for the new claim-name repair: Foundation CI
34518363609/103009403837ona3fa3470b6b4b489889b574afcc1f672a7fdedcccompleted SUCCESS. Exact checkout, Python compilation, foundation-pack/dependency-hygiene validation, owned unit/service contracts (including the new executable-container/key regressions), isolated PostgreSQL contracts, and read-only validation all passed. SAST/Security/CodeQL are still independently queued at this observation; no predecessor result is transferred.seonghobae commentedon Sep 10, 2026
Fresh #295 binding-owner finding and repair, ordinary-forward on protected
develop@eb9757f8649aaad026a9865508d9aad50c1a7a4f.Exact predecessor
a3fa3470b6b4b489889b574afcc1f672a7fdedccalready requiredextra_claimsto be an exact built-in dict with exact built-in string keys, but it did not require those keys to be canonical. Consequently an exact key such as" Access_Token "did not intersect the forbidden credential-name set afterlower()(the surrounding whitespace remained), and a blank exact string key was accepted. This is a binding-owned input-integrity defect; it does not move authentication authority from Keyverse or authorization ownership from #65.Test-first
b63c50a88f50dbeb1ea1a23eb58e781692f8427aadded padded credential-name and blank-name regressions. Foundation34530360487/103049319749is a real hosted RED on that exact test-only head: the Keyverse adapter ran 53 tests, with both new cases failing because predecessor code reachedIdentityBindingTrustUnavailableErrorinstead of rejecting the malformed claim name; 51 existing tests passed and owned keyverse-adapter production coverage remained 208 statements / 70 branches at 100%. The test-only head was then superseded normally.Minimal source successor
694585a64d038d30e851e156b8d5423e567c14f8reuses_validate_canonical_text("extra claim name", name)inside_validate_extra_claim_names()before credential-name case folding. Exact built-in dict/type checks remain; claim values remain uninterpreted and unretained. #295 was returned to Draft before these source changes and remains the sole binding/ACL writer; #65 remains the authorization writer.Current exact-head Foundation/SAST/Security/CodeQL workflows have materialized but are still queued at this read, so no GREEN is claimed yet. Keep #296 open until both canonical owner lanes integrate normally.
seonghobae commentedon Sep 10, 2026
Follow-up repair after the first source successor: exact
694585a64d038d30e851e156b8d5423e567c14f8correctly made the two new canonical-name regressions pass, but Foundation34530404135/103049459129exposed a compatibility RED in two pre-existing binding tests. The reused generic text validator changed the established bounded malformed-key error fromextra claim names must be exact textto the singularextra claim name must be exact text, sotest_extra_claims_reject_executable_container_or_key_before_useandtest_extra_claims_reject_non_text_claim_name_with_bounded_errorfailed. The same run showed the two new canonical-name tests passing and 100% production statement/branch coverage.Minimal ordinary-forward successor
625649e1770e395e3e6f44b36235d363ebf168eapreserves the existing exact-type error contract with an explicittype(name) is strgate, then delegates only exact strings to_validate_canonical_text("extra claim name", name)for blank/padded canonicality. This keeps executable string subtypes rejected before method use while adding canonical-name screening without changing the prior bounded error surface. #295 remains Draft; fresh exact-head workflows have materialized and are incomplete, so GREEN is not yet claimed.seonghobae commentedon Sep 10, 2026
Current exact successor
625649e1770e395e3e6f44b36235d363ebf168eahas now closed the repository-owned RED: Foundation34530912986/103051125781completed SUCCESS on the exact checkout. The Keyverse adapter ran 53/53 tests including both new canonical-name regressions and the two pre-existing bounded malformed-key error tests; owned adapter production coverage is 208 statements / 70 branches at 100%. The same Foundation run passed compilation, foundation/dependency hygiene, all other owned unit/service suites, isolated PostgreSQL contracts, and read-only validation. Manifest repair output was correctly skipped because this latest source/test delta is outside the deterministic manifest-tracked paths.#295 remains Draft while current-head SAST
34530913196, Security34530913081, and CodeQL34530913154complete. Do not transfer predecessor security/review evidence.seonghobae commentedon Sep 10, 2026
Fresh #295 binding-owner hardening, preserving the #295/#65 single-writer split.
Current #295 exact head is
da7d005e87c906f0ed1f0e8bd91c0677b3fff5d9on unchanged protecteddevelop@eb9757f8649aaad026a9865508d9aad50c1a7a4f, Draft while successor checks settle. Review of the exact predecessor found that credential-name screening rejectedaccess_token/refresh_tokenbut still allowed rawid_token,client_secret, andapi_keyfields to reach the missing-owner-trust path as ordinary extra claims. That contradicts the adapter's no-raw-credentials boundary; it does not require Orgmetra to verify those credentials.b15a3511c8b4eb9ad8acf8841d43a868ef253370adds regressions requiring those standard credential fields to fail before trust classification;34535505235was cancelled by the ordinary source successor before runner acquisition, so it is not represented as hosted RED;da7d005e87c906f0ed1f0e8bd91c0677b3fff5d9only extends the existing forbidden-name set withid_token,client_secret, andapi_key;Keep this issue open. Current-head Foundation/SAST/Security/CodeQL generations are fresh but queued at the latest read; no predecessor result transfers.
seonghobae commentedon Sep 10, 2026
Binding-owner update: #295 advanced ordinary-forward from
da7d005e87c906f0ed1f0e8bd91c0677b3fff5d9through test-first634daefaad125b6de49bac4e0905dec1f822ccf3to source successor086fe88607b1e30f72633b5222fbcaf8f846e9caon the unchanged protected basedevelop@eb9757f8649aaad026a9865508d9aad50c1a7a4f.Fresh audit found the exported non-authorizing
ExternalIdentityBindingCandidatewas still a frozen slots dataclass. Python's low-levelobject.__setattr__can replace an existing frozen slot after__post_init__, so a caller retaining the returned candidate could rewriteidentity_subjectafter validation. This is a validated-data integrity defect, not an authentication bypass: the candidate remains non-authorizing and there is still no positive durable-bind path.634daefa...adds a regression that a post-validation attribute-replacement attempt must fail and the subject remain unchanged. Its hosted runs were cancelled by the ordinary source successor before execution, so it is not claimed as hosted RED.086fe886...moves candidate storage to tuple-backed immutable storage, preserves the keyword constructor and public field properties, and retains exact UUID/text validation. The implementation explicitly does not treat structural immutability or Python object type as trust provenance; any future consequential consumer must reconstruct/revalidate authoritative evidence.#65 remains the authorization single writer. Do not copy this storage change into authorization or mark #296 complete until both canonical lanes reach protected integration. Fresh exact-head Foundation/SAST/Security/CodeQL generations for
086fe886...are still settling.seonghobae commentedon Sep 11, 2026
Current owner update after fresh #295 review: CodeRabbit found a valid remaining alias defect on former exact head
086fe88607b1e30f72633b5222fbcaf8f846e9ca. Tuple-backed storage still retained tenant/person as mutableUUIDobjects and returned those same objects, so caller mutation of a returned UUID.intcould change later candidate reads.#295 was returned to Draft. Test-first
7d44147ef005508c3e071d6dec10c13f68846e45adds tenant/person returned-view mutation regressions; its hosted runs were cancelled by ordinary successor publication before execution and are not claimed as hosted RED. Minimal successorc7d39a6702ed2149c79dc2aa7c2c4be983679f9cretains only checked integer scalars and returns freshUUID(int=...)views. Exact-head Foundation/SAST/Security/CodeQL are currently queued, and the live CodeRabbit thread remains unresolved until GREEN.Single-writer split is unchanged: #295 owns binding/candidate ACL; #65 owns purpose-bound authorization. Structural integrity still is not Keyverse authentication provenance.