Skip to content

feat(security): resolve current_user.email in RLS owner policies + owner-scoped showcase invoices - #2054

Merged
os-zhuang merged 3 commits into
mainfrom
claude/lucid-gauss-t7dd3z
Jun 20, 2026
Merged

feat(security): resolve current_user.email in RLS owner policies + owner-scoped showcase invoices#2054
os-zhuang merged 3 commits into
mainfrom
claude/lucid-gauss-t7dd3z

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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 on controlled_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:

  1. The RLS compiler accepts only single = (not ==).
  2. It resolves only current_user.id / organization_id / roles / org_user_idsname/email were not resolvable, so the predicate compiled to the deny sentinel (fail-closed → the user saw nothing).

The existing task_own_rows demo (assignee == current_user.name) had the same latent bug — declared but never functional, because the showcase seeds no non-admin users to exercise it.

name is the wrong anchor anyway (it collides → a collision on an ownership predicate is an access leak). email is the right one: unique (auth-enforced), seedable, readable.

Changes

Framework

  • current_user.email is now a resolvable RLS variable. Display name is deliberately excluded (documented in the compiler + a unit-test pair: email resolves, name fails-closed).
  • Email is threaded onto ExecutionContext in both identity resolvers — the REST data path (rest-server) and the dispatcher path (resolve-execution-context) — sourced from the auth session, with a bounded sys_user fallback for the API-key path.

Showcase

  • showcase_invoice gains an owner email field + an owner RLS policy (owner = current_user.email); its lines are controlled_by_parent, so the scoping flows to them.
  • Invoices + lines are seeded per owner (ada / linus / grace).
  • Fixes the previously-inert owner predicates (invoice + the old task rule) to = current_user.email.

Verification (all local, green)

  • New showcase-invoice-seed-isolation dogfood 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.
  • Full dogfood suite: 10 files / 81 tests. plugin-security104 (+2 compiler tests). rest121. example-showcase20. Full build 76/76.

Honest limit / follow-up

pnpm dev still 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

…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
@vercel

vercelBot commented Jun 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJun 20, 2026 4:53am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/m labels Jun 20, 2026
@github-actions

github-actionsBot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/dogfood, @objectstack/plugin-security, @objectstack/rest, @objectstack/runtime, @objectstack/spec.

96 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/cloud-artifact-api.mdx(via packages/runtime, packages/spec)
  • content/docs/concepts/cluster-semantics.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/implementation-status.mdx(via @objectstack/plugin-security, @objectstack/rest, @objectstack/runtime, @objectstack/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/runtime, packages/spec)
  • content/docs/concepts/packages.mdx(via @objectstack/plugin-security, @objectstack/rest, @objectstack/runtime, @objectstack/spec)
  • content/docs/concepts/setup-app.mdx(via @objectstack/spec)
  • content/docs/concepts/skills.mdx(via @objectstack/spec)
  • content/docs/concepts/webhook-delivery.mdx(via @objectstack/spec)
  • content/docs/getting-started/architecture.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/getting-started/core-concepts.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/guides/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/guides/ai-capabilities.mdx(via @objectstack/spec)
  • content/docs/guides/airtable-dashboard-analysis.mdx(via @objectstack/spec)
  • content/docs/guides/analytics-datasets.mdx(via @objectstack/spec)
  • content/docs/guides/api-reference.mdx(via @objectstack/rest, @objectstack/runtime, @objectstack/spec)
  • content/docs/guides/authentication.mdx(via @objectstack/runtime)
  • content/docs/guides/business-logic.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/error-catalog.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-type-gallery.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-validation-rules.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/permissions-matrix.mdx(via packages/plugins/plugin-security, @objectstack/spec)
  • content/docs/guides/cheatsheets/protocol-diagram.mdx(via packages/spec)
  • content/docs/guides/cheatsheets/query-cheat-sheet.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/quick-reference.mdx(via @objectstack/spec)
  • content/docs/guides/client-sdk.mdx(via @objectstack/spec)
  • content/docs/guides/cloud-deployment.mdx(via @objectstack/runtime)
  • content/docs/guides/common-patterns.mdx(via @objectstack/spec)
  • content/docs/guides/contracts/auth-service.mdx(via packages/spec)
  • content/docs/guides/contracts/cache-service.mdx(via packages/spec)
  • content/docs/guides/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/guides/contracts/index.mdx(via @objectstack/spec)
  • content/docs/guides/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/guides/contracts/storage-service.mdx(via packages/spec)
  • content/docs/guides/data-modeling.mdx(via @objectstack/spec)
  • content/docs/guides/deployment-vercel.mdx(via @objectstack/runtime, @objectstack/spec)
  • content/docs/guides/driver-configuration.mdx(via @objectstack/runtime, @objectstack/spec)
  • content/docs/guides/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/guides/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/guides/formula.mdx(via @objectstack/spec)
  • content/docs/guides/hook-bodies.mdx(via @objectstack/runtime, packages/spec)
  • content/docs/guides/kernel-services.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/dashboard.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/guides/metadata/field.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/flow.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/index.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/object.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/validation.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/workflow.mdx(via @objectstack/spec)
  • content/docs/guides/packages.mdx(via @objectstack/plugin-security, @objectstack/rest, @objectstack/runtime, @objectstack/spec)
  • content/docs/guides/plugin-chatbot-integration.mdx(via @objectstack/runtime)
  • content/docs/guides/plugin-development.mdx(via @objectstack/spec)
  • content/docs/guides/plugins.mdx(via @objectstack/plugin-security, @objectstack/rest, @objectstack/spec)
  • content/docs/guides/production-readiness.mdx(via @objectstack/runtime)
  • content/docs/guides/project-scoping.mdx(via @objectstack/spec)
  • content/docs/guides/public-forms.mdx(via @objectstack/spec)
  • content/docs/guides/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/guides/runtime-services/index.mdx(via packages/spec)
  • content/docs/guides/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/guides/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/guides/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/guides/security.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/guides/seed-data.mdx(via @objectstack/spec)
  • content/docs/guides/single-project-mode.mdx(via @objectstack/runtime)
  • content/docs/guides/skills.mdx(via @objectstack/spec)
  • content/docs/guides/standards.mdx(via @objectstack/spec)
  • content/docs/guides/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/http-protocol.mdx(via @objectstack/runtime)
  • content/docs/protocol/objectos/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/index.mdx(via @objectstack/runtime)
  • content/docs/protocol/objectos/lifecycle.mdx(via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

claude added 2 commits June 20, 2026 04:40
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
os-zhuang marked this pull request as ready for review June 20, 2026 05:42
@os-zhuang
os-zhuang merged commit 2afb612 into mainJun 20, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/lucid-gauss-t7dd3z branch June 20, 2026 05:42
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude