Skip to content

Take back the table grants a hosted project gives anon - #10

Merged
boubou666 merged 1 commit into
mainfrom
worktree-anon-grant
Aug 5, 2026
Merged

boubou666 merged 1 commit into
mainfrom
worktree-anon-grant

Conversation

@boubou666

@boubou666 boubou666 commented Aug 5, 2026 •

Copy link
Copy Markdown
Owner

The RLS suite was pointed at a hosted Supabase project for the first time. 30 of 31 passed; no session sees nothing at all failed, because the select succeeded where it expected permission denied.

20260804101204_boards.sql grants each table to authenticated and says in a comment that anon is granted nothing. That is true on the local stack and false on a hosted project, which ships

alter default privileges for role postgres in schema public
  grant all on tables to anon, authenticated, service_role;

and creates the tables as postgres when supabase db push applies them. So every table arrived carrying grant all ... to anon. Locally the migrations are applied by another role, the default privilege never fires, and the two environments disagreed in silence.

Nothing was exposed. All twelve policies are to authenticated, so RLS finds no policy for anon and default-denies — the select returned no rows rather than refusing. What was missing is the layer beneath the policies: the one that refuses outright, and so would still hold if a policy were later written too loosely.

Changes

  • 20260805153000_revoke_anon_table_grants.sql revokes anon on boards, board_members and board_invites, stating the privilege in SQL instead of inheriting it from whichever role ran the file.
  • test/db/rls.test.js asserts has_table_privilege('anon', …) directly for the three tables. The behavioural test cannot guard this on its own: locally it passes whether or not the revoke exists, because locally the grant never existed. Same reasoning as the existing prosecdef assertion.
  • The comment at 20260804101204_boards.sql:136 no longer claims something untrue of production.

Verification

Applied to the hosted project and read back with supabase db dump: no anon table grants remain, and the authenticated grants are intact on all three tables.

Known, deliberately not addressed

  • A table added later will arrive with the same grant and needs the same revoke. Changing the project's default privileges instead would be a wider blast radius than this fix warrants.
  • The trigger functions (bump_board_version, freeze_board_owner, touch_updated_at) still carry grant all ... to anon. Postgres refuses direct calls to trigger functions, but whether revoking execute affects trigger firing was not verified, and production was not the place to find out.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TbSwVkGcNQZohV9VoeACRT

Summary by CodeRabbit

  • Bug Fixes

    • Restricted anonymous access to boards, board members, and board invitations.
    • Prevented unauthenticated users from selecting, creating, updating, or deleting records in these tables.
  • Tests

    • Added coverage to verify anonymous users have no table privileges.

@coderabbitai

coderabbitai Bot commented Aug 5, 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: e8da9a3d-220a-4e8a-860c-f325f1830b37

📥 Commits

Reviewing files that changed from the base of the PR and between d001b29 and a0cc2fb.

📒 Files selected for processing (3)
  • supabase/migrations/20260804101204_boards.sql
  • supabase/migrations/20260805153000_revoke_anon_table_grants.sql
  • test/db/rls.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • supabase/migrations/20260805153000_revoke_anon_table_grants.sql
  • supabase/migrations/20260804101204_boards.sql

📝 Walkthrough

Walkthrough

The database migrations revoke all anon privileges from three public board tables. The access-control comments document this behavior. The RLS tests verify that anon has no table privileges.

Changes

Anonymous privilege control

Layer / File(s) Summary
Privilege revocation
supabase/migrations/20260804101204_boards.sql, supabase/migrations/20260805153000_revoke_anon_table_grants.sql
The migrations document hosted-project defaults and revoke all anon privileges from public.boards, public.board_members, and public.board_invites.
Privilege validation
test/db/rls.test.js
The RLS tests verify that anon has no select, insert, update, or delete privileges on the three tables.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • boubou666/peerthink#5: Adds the related boards migration and RLS test coverage that this change extends.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes revoking the table privileges that hosted projects grant to the anon role.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 worktree-anon-grant

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

The RLS suite failed its first run against a hosted project: `no session
sees nothing at all` expected `permission denied` and got a successful
select. `20260804101204_boards.sql` grants each table to `authenticated`
and says anon is granted nothing, which is true locally and false in
production — Supabase ships

  alter default privileges for role postgres in schema public
    grant all on tables to anon, authenticated, service_role;

and `db push` creates the tables as `postgres`, so each one arrived with
`grant all ... to anon`. Locally another role applies the migrations, the
default privilege never fires, and the two environments disagreed in
silence.

Nothing was exposed. Every policy is `to authenticated`, so RLS finds no
policy for anon and default-denies; the select returned no rows rather
than refusing. What was missing is the layer beneath the policies, the
one that would still hold if a policy were later written too loosely.

The behavioural test cannot guard this on its own — it passes locally
whether or not the revoke exists, because locally the grant never existed.
So the privilege is asserted directly, for the same reason `prosecdef` is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbSwVkGcNQZohV9VoeACRT
@boubou666
boubou666 force-pushed the worktree-anon-grant branch from 01b3429 to a0cc2fb Compare August 5, 2026 16:36
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@boubou666
boubou666 merged commit e0e9fa8 into main Aug 5, 2026
2 checks passed
@boubou666
boubou666 deleted the worktree-anon-grant branch August 5, 2026 16:52
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