Skip to content

fix(supabase): scope schema.sql write policies to service_role (GH#2499) - #2500

Open
dcccrypto wants to merge 1 commit into
playgroundfrom
fix/2499-schema-rls-service-role
Open

fix(supabase): scope schema.sql write policies to service_role (GH#2499)#2500
dcccrypto wants to merge 1 commit into
playgroundfrom
fix/2499-schema-rls-service-role

Conversation

@dcccrypto

@dcccrypto dcccrypto commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Fixes #2499.

The premise is correct. The claimed production impact is already mitigated — and I'd rather say so than let a High sit on the board unqualified.

Confirmed: all six write policies in supabase/schema.sql lacked a TO
clause, and a policy without one applies to the PUBLIC pseudo-role, which includes
anon. The report's reading of the SQL is exactly right.

Not confirmed — the production exposure. The report audits schema.sql but
not supabase/migrations/, and the migration chain already closes this. Migration
021_fix_rls_policies.sql created *_service_only policies TO service_role, and
20260402180100_drop_stale_core_table_rls_policies.sql dropped these six by name.
Its header describes the identical finding, four months ahead of this report:

schema.sql … created INSERT/UPDATE policies … with NO TO clause — defaulting
to the PUBLIC pseudo-role (all roles including anon) … An attacker with the
public anon key could INSERT fake markets/trades or UPDATE market_stats/oracle_prices.

It also records that migration 021's own first attempt was a silent no-op — it
issued DROP POLICY against names (markets_insert) that never existed — which is
why a second migration was needed. Worth knowing: this class has bitten twice in a
way that looked fixed.

I swept the rest of the schema for the same shape rather than assuming these six
were it. Three more tables had it, and all three are also already fixed:

table stale policy dropped by
markets/market_stats/trades/oracle_prices 6 policies 20260402180100
bug_reports insert + update 20260402180000
ideas Service can update ideas 20260330120000
job_applications Service can update applications 044_fix_admin_users_rls

So on any database built from the migration chain, nothing is open. The CVSS 8.6
doesn't apply to a migrated deployment.

What is still open, and is worth fixing

schema.sql itself. As 20260402180100's comment puts it, it is "the reference
file sometimes run via Supabase SQL Editor"
— and PostgreSQL ORs permissive
policies. Running the old version against a migrated database re-creates the
wide-open policies alongside the correct ones and silently re-opens write access:
precisely what that migration exists to undo. A fresh environment bootstrapped from
it starts vulnerable outright.

That's a real footgun with a one-line-per-policy fix, so this PR fixes it at the
source. The UPDATE policies also gain with check (true) to match 021's
service_role versions — without it an UPDATE can read a row it may not write back.

Test scope, deliberately narrow

The test pins schema.sql only. Historical migrations contain the original
CREATEs and always will; asserting over them would flag immutable history forever
and pressure someone into editing an applied migration — worse than the thing being
prevented. The comment in the test says this, so the next person doesn't "fix" the
gap by widening the sweep.

Mutation-verified: dropping to service_role from any single policy fails 2 tests.

cd app && npx vitest run
Test Files  282 passed | 1 skipped (283)
     Tests  2926 passed | 16 skipped (2942)

Suggested follow-up, not in this PR

The report includes a verification query for checking live policy state. Running it
against each deployed Supabase project is still worth doing — my analysis covers
what the repo produces, and cannot tell you whether some environment had
schema.sql pasted into it by hand after its migrations ran. That's the one
scenario where the report's original severity would hold, and it's answerable only
against the live databases.

Summary by CodeRabbit

  • Bug Fixes

    • Strengthened database write permissions so protected updates are limited to authorized service operations.
    • Prevented public access from creating or modifying protected records.
    • Preserved public read access where intended.
    • Ensured market and statistics updates continue to accept valid service-managed changes while maintaining the updated access restrictions.
  • Tests

    • Added regression coverage to verify write policies remain properly restricted and public read policies remain available.

The six write policies in supabase/schema.sql carried no `TO` clause. A
PostgreSQL RLS policy without one applies to the PUBLIC pseudo-role, which
includes `anon` — the role behind NEXT_PUBLIC_SUPABASE_ANON_KEY — so they
authorise INSERT/UPDATE on markets, market_stats, trades and oracle_prices
for anyone holding the public key, straight through PostgREST and around
every check in the API routes.

Scope, verified before changing anything: a database built from
supabase/migrations/ is NOT affected. Migration 021 added
service_role-only policies, and 20260402180100 dropped these six by name.
The same defect had already been found and fixed three other times —
bug_reports (20260402180000), ideas (20260330120000), job_applications
(044) — so every instance in the migration chain is closed.

What was still open is this file. As 20260402180100's own comment records,
schema.sql is "the reference file sometimes run via Supabase SQL Editor",
and PostgreSQL OR's permissive policies: running the old version against a
migrated database would re-create the wide-open policies alongside the
correct ones and silently re-open write access — exactly what that
migration exists to undo. Fixed at the source so a fresh bootstrap, or a
re-run, is safe.

The UPDATE policies also gain `with check (true)` to match the
service_role-only versions in migration 021; without it an UPDATE can read
a row it may not write back.

Test pins schema.sql only, and says why: historical migrations contain the
original CREATEs and always will. Asserting over them would flag immutable
history forever and pressure someone into editing an applied migration,
which is worse than the thing being prevented.

Mutation-verified: dropping `to service_role` from any one policy fails 2
tests.

Suite: 2926 passed | 16 skipped, 0 failed.
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
percolator-launch Ready Ready Preview Aug 6, 2026 1:41am
percolator-mainnet Canceled Canceled Aug 6, 2026 1:41am
percolator-playground Ready Ready Preview Aug 6, 2026 1:41am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 30c43449-d2a3-45a4-8517-0e99970ece9d

📥 Commits

Reviewing files that changed from the base of the PR and between f2a3bbe and 4aa90e0.

📒 Files selected for processing (2)
  • app/__tests__/lib/supabase-rls-write-policies.test.ts
  • supabase/schema.sql

📝 Walkthrough

Walkthrough

Supabase financial-table write policies now explicitly target service_role. Update policies retain with check (true). A Vitest regression test parses supabase/schema.sql, rejects unscoped write policies, verifies selected table policies, and preserves public SELECT policies.

Changes

RLS policy hardening

Layer / File(s) Summary
Restrict financial-table writes
supabase/schema.sql
Market, stats, trade, and price write policies now target service_role. Update policies explicitly retain with check (true).
Validate policy role scoping
app/__tests__/lib/supabase-rls-write-policies.test.ts
The regression test removes comments, parses write policies, rejects missing roles, checks four tables for service_role, and confirms public SELECT policies remain.

Estimated code review effort: 3 (Moderate) | ~15–30 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The schema and regression test meet the service_role policy objective, but no privilege revocations or authoritative migration changes are shown [#2499]. Add or document compatible table privilege revocations and authoritative migration coverage for the affected financial tables.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the Supabase schema change and the service_role policy scope.
Out of Scope Changes check ✅ Passed The schema policy updates and regression test directly support the linked security issue objectives [#2499].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/2499-schema-rls-service-role

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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.

1 participant