Add Row-Level Security (RLS) for per-user isolation - #47
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Implements Phase 1 of per-user isolation using PostgreSQL Row-Level Security (RLS) in the pg_durable extension, plus accompanying privilege hardening, monitoring updates, and E2E coverage.
Changes:
- Enable RLS on
df.instancesanddf.nodesand auto-grant a hardened set of privileges toPUBLICduringCREATE EXTENSION. - Add RLS-based ownership checks to
df.cancel()/df.signal(), and update monitoring functions to only query duroxide for instance IDs visible via SPI (RLS-filtered). - Update E2E setup/user-isolation tests and add a dedicated RLS E2E suite; refresh docs describing the new security model and multi-user setup.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/lib.rs |
Adds RLS enablement + policies and default GRANT model during extension install. |
src/dsl.rs |
Adds ownership checks for cancel/signal and updates cancel’s status update path. |
src/monitoring.rs |
Reworks instance listing/info/executions to respect RLS visibility before calling duroxide APIs. |
tests/e2e/sql/36_rls.sql |
New E2E coverage validating RLS isolation, denials, and superuser bypass. |
tests/e2e/sql/00_setup_playground.sql |
Removes manual grants helper logic (now relying on extension auto-grants). |
tests/e2e/sql/27_user_isolation.sql |
Removes redundant GRANTs and aligns with new auto-grant model. |
scripts/test-e2e-local.sh |
Runs 36_rls as superuser due to session authorization switching. |
docs/rls.md |
Adds the RLS design/spec and grant strategy documentation. |
docs/user-isolation.md |
Updates identity capture narrative to match current implementation (nodes inserted in df.start()). |
USER_GUIDE.md |
Documents RLS behavior, default grants, and how to restrict access from PUBLIC. |
README.md |
Adds “Multi-User Setup” section describing RLS + default grants. |
.github/copilot-instructions.md |
Updates E2E invocation examples to include --verbose. |
Pino de Candia (pinodeca)
force-pushed
the
pinodeca/rls
branch
from
March 10, 2026 19:52
43cd1bd to
bcdb75e
Compare
- Enable RLS on df.instances and df.nodes with user isolation policies - Auto-GRANT permissions to PUBLIC (UPDATE on 2 columns, no DELETE) - Add ownership checks to df.cancel() and df.signal() - Rework list_instances(), instance_info(), instance_executions() to query df.instances via SPI first (RLS-filtered) before duroxide client - Update E2E setup to reflect automatic grants - New 37_rls.sql E2E test covers instance/node isolation, cancel/signal denial, monitoring RLS, superuser bypass, and DELETE denial
Pino de Candia (pinodeca)
force-pushed
the
pinodeca/rls
branch
from
March 10, 2026 21:51
fa82f1b to
c72f2d3
Compare
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.
Summary
Implements Phase 1 of the RLS spec (docs/rls.md) to enforce per-user isolation at the PostgreSQL table level, with hardened grant model.
Changes
Core RLS & Grants (
src/lib.rs):df.instancesanddf.nodeswithinstances_user_isolationandnodes_user_isolationpolicies (submitted_by = current_user::regrole)PUBLIC:SELECT, INSERTon instances/nodes, column-levelUPDATE (status, updated_at)on instances (fordf.cancel()), full DML on vars, schema usage, and function executionUPDATE— identity columns (submitted_by,login_role) and structural columns (root_node) are protected from user modificationDELETEon instances or nodesFORCE ROW LEVEL SECURITY— superusers bypass RLS (standard PostgreSQL convention)Ownership checks (
src/dsl.rs):df.cancel(): SPI ownership check via RLS before calling duroxide client, then SPI UPDATE on status (column-level grant)df.signal(): Same ownership check beforeraise_external_event()Monitoring function rework (
src/monitoring.rs):list_instances(): Queriesdf.instancesvia SPI first (RLS-filtered), then only fetches duroxide info for user's own instance IDsinstance_info(): RLS-based ownership check — returns empty for non-owned instancesinstance_executions(): RLS-based ownership check — returns empty for non-owned instancesinstance_nodes(): No changes needed (already SPI-filtered)metrics(): No changes needed (aggregate-only, no per-instance data)E2E tests (
tests/e2e/sql/):00_setup_playground.sql—_e2e_grant_df_to_e2e_user()is now a no-op (grants are automatic)27_user_isolation.sql— removed redundant explicit GRANTs36_rls.sql— 9 tests: instance isolation, node isolation, cancel/signal denial, monitoring RLS, superuser bypass, UPDATE denial on identity columns, DELETE denial, cross-user UPDATE denialDocs:
docs/rls.mdDecision 8 — column-level UPDATE grant modelREADME.md— added Multi-User Setup sectionUSER_GUIDE.md— updated Privilege Grants, Cross-Instance Visibility, Security Best Practices sectionsSecurity model
df.instances(status, updated_at)only (RLS)df.nodesdf.varsUsers cannot modify
submitted_by,login_role,root_node, or any column ondf.nodes. RLS restricts all operations to the user's own rows.Not included (deferred per spec)
df.varsRLS) — Phase 2 follow-up PRTest results
36_rlswith 9 sub-tests)