feat(security): resolve current_user.email in RLS owner policies + owner-scoped showcase invoices#2054
Merged
Merged
Conversation
…owcase owner-scoped invoices RLS `using` predicates can now reference `current_user.email` — a unique, seedable owner anchor (`owner = current_user.email`). The RLS compiler previously resolved only id/organization_id/roles/org_user_ids, so any owner-by-email predicate compiled to the deny sentinel (fail-closed → user saw nothing). Email is sourced from the auth session (bounded sys_user fallback for the API-key path) and threaded onto ExecutionContext in BOTH identity resolvers: the REST data path (rest-server) and the dispatcher path (resolve-execution-context). Display `name` is deliberately NOT exposed to RLS — names collide, and a collision on an ownership predicate is an access-control leak. Only unique identifiers (id, email) are resolvable; documented in the compiler + a unit test pair. Combined with controlled_by_parent (ADR-0055), a master's owner scoping now flows to its detail records over seed data (no per-user ids needed). The example-showcase demonstrates it: showcase_invoice carries an `owner` email + owner RLS, its lines are controlled-by-parent, and invoices/lines are seeded per owner. Also fixes the showcase's previously inert owner predicates (they used `==` and current_user.name — neither accepted by the compiler) to `= current_user.email`. Verified end-to-end through the real HTTP stack: a new showcase-invoice-seed-isolation dogfood proof asserts a contributor reads only their own invoices and (derived) their own lines, not another owner's. Full dogfood suite 10 files / 81 tests; plugin-security 104; rest 121; showcase 20. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 5 package(s): 96 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The objectql security reference listed `current_user.role`/`current_user.department` "plus any custom user field" as available RLS variables — but the compiler resolves only unique identifiers + pre-resolved membership sets. Corrected to the actual set (`id`, `email`, `organization_id` for equality; `org_user_ids`/`roles`/rlsMembership for IN) and documents that display `name`/arbitrary fields are intentionally not resolvable (collision = access leak). Reflects the new `current_user.email` support. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
#2052 made `userActions.rowHeight`/`addRecordForm` required on list capabilities and updated most pages, but `active-projects.page.ts` and `task-visualizations.pages.ts` were missed — failing `tsc --noEmit` (TypeScript Type Check) on every PR. Adds the two booleans (`false`, matching the curated-page convention in task-triage/workbench), and `filter: false` on the visualization variants that omitted it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
os-zhuang
marked this pull request as ready for review
June 20, 2026 05:42
This was referenced Jun 20, 2026
os-zhuang
added a commit
that referenced
this pull request
Jun 20, 2026
…ngs (#2075) The nine ADR-0056 PRs merged but updated only one doc line (the email-RLS note in #2054). This brings the hand-written security docs and the data authoring skill in line with what actually shipped and is dogfood-proven: - public-forms: public forms are now self-authorizing via a declaration- derived `publicFormGrant` (create + read-back on the form's target object). A `guest_portal` profile is no longer required — it is retained only for back-compat (hooks detecting a guest via falsy `ctx.user?.id`). (ADR-0056 Option A, #2073) - implementation-status + security guide: corrected the "anonymous traffic bypasses enforcement" posture (boot warning lands in D2; public forms no longer depend on the fall-open; the default-deny flip is release-gated); marked OWD/sharing-model enforcement, the sharing-rule evaluator, and the app-declarable default profile as live. - security guide: documented the `role_and_subordinates` sharing recipient (configurable role-hierarchy widening, D6). - permissions-matrix: `object.sharingModel` now accepts the canonical OWD vocabulary (`private`/`public_read`/`public_read_write`/ `controlled_by_parent`) alongside the legacy spellings (D1). - objectstack-data skill: corrected the RLS section to the enforced `rowLevelSecurity` policy shape (`using`/`check` + `current_user.*` placeholders, incl. the new `current_user.email`), and flagged the object-level CEL `rls` config as experimental (D8). Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx Co-authored-by: Claude <noreply@anthropic.com>
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.
What
Makes RLS owner policies work with
current_user.email— a unique, human-readable, seedable owner anchor — and uses it to ship an out-of-the-box owner-scoped scenario in the example-showcase, building oncontrolled_by_parent(ADR-0055).Why
While wiring an owner-RLS demo into the showcase, the showcase's owner predicate (
owner == current_user.name) was found to be doubly inert:=(not==).current_user.id/organization_id/roles/org_user_ids—name/emailwere not resolvable, so the predicate compiled to the deny sentinel (fail-closed → the user saw nothing).The existing
task_own_rowsdemo (assignee == current_user.name) had the same latent bug — declared but never functional, because the showcase seeds no non-admin users to exercise it.nameis the wrong anchor anyway (it collides → a collision on an ownership predicate is an access leak).emailis the right one: unique (auth-enforced), seedable, readable.Changes
Framework
current_user.emailis now a resolvable RLS variable. Displaynameis deliberately excluded (documented in the compiler + a unit-test pair: email resolves, name fails-closed).ExecutionContextin both identity resolvers — the REST data path (rest-server) and the dispatcher path (resolve-execution-context) — sourced from the auth session, with a boundedsys_userfallback for the API-key path.Showcase
showcase_invoicegains anowneremail field + an owner RLS policy (owner = current_user.email); its lines arecontrolled_by_parent, so the scoping flows to them.= current_user.email.Verification (all local, green)
showcase-invoice-seed-isolationdogfood proof — boots the real showcase and asserts, over the seeded data through the real HTTP stack, that a contributor reads only their own invoices and (derived) their own lines, never another owner's. 4/4.plugin-security104 (+2 compiler tests).rest121.example-showcase20. Full build 76/76.Honest limit / follow-up
pnpm devstill seeds only the admin (who sees everything). Observing the isolation requires logging in as a contributor-scoped user. Making any new sign-up auto-owner-scoped would need the dev-plugin to let an app declare its fallback/default permission set — a separate posture change, not included here.🤖 Generated with Claude Code
Generated by Claude Code