Skip to content

test(db): cover RLS on the nine tables that had no pgTAP tests - #80

Merged
Programcode merged 2 commits into
mainfrom
t3code/pgtap-coverage-untested-tables
Oct 1, 2026
Merged

Programcode merged 2 commits into
mainfrom
t3code/pgtap-coverage-untested-tables

Conversation

@Programcode

@Programcode Programcode commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

Nine public tables had row-level security enabled, but nothing in supabase/tests/ asserted their policies. This PR adds five pgTAP files that cover them. It changes no schema, policy or application code.

  • user_roles_rls_test.sql. A user cannot promote themselves to admin through an update or an upsert. They cannot create an admin row for themselves or for anyone else. They cannot demote or delete an admin, and a banned user cannot delete or change their ban. After these attempts, get_active_rate_limits() still rejects the user.
  • shared_items_rls_test.sql. Clients have no direct table privileges. Anonymous and signed-in callers read a share only through get_shared_item(code). That function matches the code exactly, skips expired shares, does not return created_by, and changes nothing except view_count.
  • owner_scoped_tables_rls_test.sql (api_keys, source_files). A user can read, create, update and delete their own rows. They cannot read, plant, replace, reassign or delete another user's rows. Anonymous callers see nothing.
  • admin_tables_rls_test.sql (admin_grants, rate_limits, global_prompts). Only admins can read the grant log, and grantees cannot read it either. Users see their own rate limits and the global ones, but not other users' limits. No client role, admins included, can write grants, limits or prompts, because those writes go through the admin Edge Function.
  • server_only_tables_rls_test.sql (webhook_events, user_data_clear_markers). anon and authenticated hold no privileges, see zero rows, and cannot insert, update or delete.

Under RLS, a denied UPDATE or DELETE can either raise a privilege error or match no rows. The tests count changed rows and treat a privilege error as zero, so they still pass if a later migration revokes a grant.

Differences from the task description

  • The task described shared_items as "public read only through the share code, owner-only write". Since 20260819000000_security_hardening.sql, owners cannot write shares directly. Only the share Edge Function writes them, using the service role. The tests assert that current model.
  • api_keys and source_files are owner-scoped CRUD tables, not deny-all, so their tests check owner isolation.

Open findings, not fixed here

  • global_prompts_select_all lets anon and every signed-in user read every global system prompt. Only Edge Functions read this table. The tests leave reads unasserted so they don't lock this behavior in.
  • rate_limits_select_own also exposes global rows (user_id IS NULL) to anon.
  • After a fresh migration replay, anon and authenticated hold TRUNCATE, TRIGGER and REFERENCES on user_roles, admin_grants, rate_limits, global_prompts, api_keys and source_files, from Supabase's default privileges. RLS does not apply to TRUNCATE. PostgREST does not expose it, so clients cannot reach it today. I did not check production grants.

Verification

  • All 18 pgTAP files (423 assertions) pass against a fresh local migration replay.
  • Mutation check: I added permissive policies and grants to each of the nine tables, and every new file failed (6, 7, 15, 14 and 11 failed assertions). After I reverted them, the full suite passed again.
  • npm run docs:check passes.
  • ./.superset/db-gate.sh: see the comment below for the locked gate result at this head.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Added database security tests covering role-based access to administrative settings, API keys, source files, shared items, and user roles.
    • Verified that anonymous and authenticated users cannot access or change protected data beyond their permissions, while permitted users retain access to their own records.
    • Added checks for server-only data protections, shared-item lookup behavior, and restrictions on administrative actions. The tests also confirm that unauthorized attempts leave protected records unchanged.

shared_items, user_roles, admin_grants, api_keys, source_files,
rate_limits, global_prompts, webhook_events and user_data_clear_markers
all had row-level security enabled, but no test asserted their policies.

The new tests check that:
- a user cannot grant themselves admin, demote an admin, or lift their
  own ban;
- shares can be read only through get_shared_item(code), and clients
  cannot read or write the table directly;
- users can manage their own API keys and source files but cannot read,
  plant, move or delete anyone else's;
- only admins read the grant log, users see only their own and global
  rate limits, and no client role can write grants, limits or prompts;
- clients see no webhook events or clear markers and cannot write them.

Denied UPDATE and DELETE statements must change zero rows, whether the
table lacks the privilege or RLS filters the rows out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 1, 2026 08:54
@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
debate-navigator Ready Ready Preview Oct 1, 2026 9:14am UTC

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: c060ff3e-6649-439e-96dd-38bb46a7b965

📥 Commits

Reviewing files that changed from the base of the PR and between fe6100a and 7ceafbd.

📒 Files selected for processing (5)
  • supabase/tests/admin_tables_rls_test.sql
  • supabase/tests/owner_scoped_tables_rls_test.sql
  • supabase/tests/server_only_tables_rls_test.sql
  • supabase/tests/shared_items_rls_test.sql
  • supabase/tests/user_roles_rls_test.sql

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The pull request adds five transactional pgTAP suites. They test role-specific access, write restrictions, and seeded-record preservation for administrative tables, server-only tables, owner-scoped records, shared items, and user roles.

Changes

Supabase RLS test coverage

Layer / File(s) Summary
Administrative and server-only table access
supabase/tests/admin_tables_rls_test.sql, supabase/tests/server_only_tables_rls_test.sql
Tests role-specific privileges and access to grants, rate limits, prompts, webhook events, and clear markers. Assertions check that client attempts do not change seeded records.
Owner-scoped API keys and source files
supabase/tests/owner_scoped_tables_rls_test.sql
Tests authenticated access to a user’s own records, restrictions on other users’ records, anonymous access, and preservation of seeded data.
Shared-item access and code lookup
supabase/tests/shared_items_rls_test.sql
Tests direct table access and code-based lookups for anonymous and authenticated roles, including expired shares and final share state.
User-role access and role changes
supabase/tests/user_roles_rls_test.sql
Tests role visibility and restrictions on role changes across authenticated, administrator, banned, role-less, and anonymous accounts.

Estimated code review effort: 3 (Moderate) | ~25 minutes

  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 57 minutes.

has_table_privilege returns true when the role holds any privilege in a
comma-separated list. The service-role checks for shared_items,
webhook_events and user_data_clear_markers still passed after INSERT,
UPDATE and DELETE were revoked, as long as SELECT remained. They now
require every privilege. Revoking INSERT from service_role makes both
checks fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Programcode

Copy link
Copy Markdown
Owner Author

Notes on the first head (38f296a):

  • A Codex review found that the positive service_role checks passed a privilege list to has_table_privilege. A list means "any of", so the checks still passed with only SELECT. 7ceafbd checks each privilege separately. I confirmed both checks now fail after revoking INSERT from service_role.
  • Three older tests have the same pattern and are not changed here: demo_access_codes_rls_test.sql:25, current_debates_test.sql:9-11 and debate_context_pins_test.sql:9-12.
  • live-e2e failed on five live OpenRouter journeys, with timeouts and missing UI elements. That workflow has failed on every recent run, including main's scheduled runs on 09-29 and 09-30. Live mode skips pgTAP, so this PR cannot affect it.
  • CI database-tests passed at 38f296a. 18 files and 423 assertions passed, including the five new files.

@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@Programcode

Copy link
Copy Markdown
Owner Author

Locked gate result at 7ceafbd (local pass, not CI):

  • ./.superset/db-gate.sh exited 0 at 10:21 UTC. pgTAP ran 18 files and 423 assertions, all passing, including the five new files. Full-stack journeys: 55 passed, 1 skipped.
  • An earlier local gate run at 38f296a also passed pgTAP. Its browser journeys failed partway through, with ECONNREFUSED 127.0.0.1:18090, after the shared fake-provider container disappeared. The run at 7ceafbd above is clean.

CI at 7ceafbd: database-tests passed with 423 pgTAP assertions and 55 full-stack journeys. quality, browser-tests and Vercel also passed. live-e2e failed on the same five live OpenRouter journeys that fail on main.

CodeRabbit ran a full review of all five files at 7ceafbd and reported no findings. Copilot was out of quota.

@Programcode
Programcode merged commit 34a2c1e into main Oct 1, 2026
7 of 8 checks passed

This branch was successfully deployed

1 active deployment
Preview — 7ceafbd5 Deployed Oct 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants