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-security 104 (+2 compiler tests). rest 121. example-showcase 20. 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

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 20, 2026 4:53am

Request Review

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

github-actions Bot 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 main Jun 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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants