fix(authz): widen the driver's native tenant scope to the group membership union — ADR-0105 D2 reaches the wire (#3623)#3631
Merged
Conversation
…rship union — ADR-0105 D2 reaches the wire (#3623) The Layer 0 wall correctly compiled organization_id IN accessible_org_ids under the group posture, but the engine also propagated the active-org tenantId into DriverOptions unconditionally, and the SQL driver's native scoping ANDed organization_id = tenantId under the union — collapsing every group read back to active-org (isolated) reach. Found by the cloud-side ee-group-showcase dogfood (cloud#880), the first end-to-end boot of group against a real driver. - spec: DriverOptions.tenantIds — the union tenant access set. Native scoping widens reads/updates/deletes/aggregates to IN (...), keeping the NULL-tenant global-row carve-out (#2734); inserts still stamp from tenantId (active org = write target, D5). Absent/empty => equality fallback: fail toward isolation, never toward exposure. - objectql: setTenancyPostureProvider seam + buildDriverOptions threads ExecutionContext.accessible_org_ids as tenantIds when the posture is group. - driver-sql: applyTenantScope honors tenantIds via whereIn at the single read-side chokepoint. - plugin-security: wires the provider at start() — from the enforcement layer on purpose, so the driver wall only widens while the Layer 0 union wall enforces above it; embeddings without plugin-security keep equality. Verified: objectql 1091, driver-sql 293, plugin-security 593, spec 6672 tests green; spec docs regenerated (check:docs in sync); repo grep guards clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 113 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 27, 2026 11:55
21 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3623. Part of ADR-0105 tracking #3541; found by the cloud-side
ee-group-showcasedogfood (objectstack-ai/cloud#880, objectstack-ai/cloud#882) — the first end-to-end boot ofgroupposture against a real driver.The bug
The Layer 0 wall correctly compiled
organization_id IN accessible_org_idsundergroup(verified live: posture and the full set reachcomputeLayeredRlsFilter), butbuildDriverOptionsalso propagated the active-orgtenantIdintoDriverOptionsunconditionally, and the SQL driver's native scoping then ANDedorganization_id = tenantIdunder the union — collapsing every group read back to active-org (isolated) reach. Symptom: a group member's read reach moved withorganization/set-active; union semantics never reached the wire.The fix — widen the wall, don't remove it
The driver's native scope is defense-in-depth and stays; under
groupit now scopes to the SAME union Layer 0 enforces:DriverOptions.tenantIds: the union tenant access set. Semantics documented on the schema: non-empty ⇒ native scoping widens reads/updates/deletes/aggregates toIN (...)(keeping the Freshobjectstack devboot: tenant admin sees ZERO rows in sys_position / sys_permission_set / sys_business_unit over REST (Setup Access Control renders empty) #2734 NULL-tenant global-row carve-out); absent/empty ⇒tenantIdequality fallback — fail toward isolation, never toward exposure; insert injection ALWAYS usestenantId(active org = write target, D5). Generatedcontent/docs/references/data/driver.mdxregenerated.setTenancyPostureProviderseam (mirrorssetCryptoProvider), andbuildDriverOptionsthreadsExecutionContext.accessible_org_idsastenantIdswhen the provider reportsgroup.applyTenantScope(the single read-side chokepoint) honorstenantIdsviawhereIn, filtering malformed entries and falling back to equality on an empty set.start(), right where the posture is cached from thetenancyservice. Deliberately wired from the enforcement layer: widening the driver wall is only safe while the Layer 0 union wall enforces above it, so an embedding without SecurityPlugin never widens, and an entitlement degrade (posture →single) stops the widening live through the same provider.Verification
objectql1091,driver-sql293,plugin-security593,spec6672 — all green.tenantIds; no provider / isolated / empty set ⇒ equality only), driver union scoping (spans exactly the listed tenants, outside-set rows invisible for read AND update, Freshobjectstack devboot: tenant admin sees ZERO rows in sys_position / sys_permission_set / sys_business_unit over REST (Setup Access Control renders empty) #2734 carve-out preserved, empty/malformed set ⇒ equality, insert still stamps fromtenantId), SecurityPlugin wiring (live posture via the provider; older engine without the seam tolerated).check:docsin sync; changeset included (patch ×4).Follow-up once merged
Bump cloud's
.objectstack-shapast this — the twoit.failspins in cloud'see-group-showcase(group member reads ALL plants,read reach is membership-wide) will flip RED, which is the designed signal to un-pin them and assert the real union semantics end-to-end.🤖 Generated with Claude Code
https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL
Generated by Claude Code