Skip to content

fix(db): stop game read policies recursing through game_players - #29

Merged
verlyn13 merged 1 commit into
mainfrom
fix/game-access-policies
Sep 14, 2026
Merged

verlyn13 merged 1 commit into
mainfrom
fix/game-access-policies

Conversation

@verlyn13

Copy link
Copy Markdown
Member

Summary

Roadmap section 1 step 2: game read policies no longer recurse.

Reproduced locally as a signed-in user: select on game_players, games, domain_events and rooms each failed with infinite recursion detected in policy for relation "game_players". The game_players SELECT policy queried game_players, and the games SELECT policy queried game_players back. Game history, game details and analysis in the web app would have failed as soon as games started saving (#28).

  • supabase/migrations/20260914000002_game_access_policies.sql:
    • public.is_game_participant(game_id): SECURITY DEFINER, STABLE, search_path = ''. It is true when auth.uid() holds a human seat in the game.
    • EXECUTE is revoked from PUBLIC and granted to anon, authenticated and service_role, because policies run with the caller's privileges.
    • The game_players and games SELECT policies are rewritten to use it, with the same access rules: participants, waiting games, and spectator-enabled games in the listed statuses. domain_events and rooms work again unchanged.
  • supabase/tests/game_access_policies.sql (15 tests):
    • a seated player sees every seat of their game, AI seats included, plus waiting and spectator-enabled games;
    • an outsider and an anonymous client see neither the private game nor its seats;
    • reads of domain_events and rooms succeed;
    • the function's definer and grant shape.
  • Docs: roadmap section 1 drops the finished step; the release step and status action 3 apply 20260914000001 and 20260914000002.

The spectator clauses still name a playing status that the games check never allows. That is preserved as-is here; the roadmap's Supabase minimization removes the spectator policies.

No migration applied, nothing deployed.

Verification

  • Local stack:
    • Recursion reproduced before the migration; the four reads succeed after it.
    • supabase test db passes with the new file.
    • Hosted-like order (000001, 20260914000001, this migration, without 000002): applies cleanly, and the new test file passes.
    • A final full reset in normal order passes the full suite.
  • node scripts/check-docs.mjs, git diff --check, the public-safety scan, pre-push pnpm validate:ci, and CI and CodeQL on this PR.

Signed-in reads of game_players, games, domain_events and rooms failed with 'infinite recursion detected in policy for relation game_players': the game_players SELECT policy queried game_players, and the games SELECT policy queried game_players back. Migration 20260914000002 adds is_game_participant(game_id), a SECURITY DEFINER check with an empty search_path that reads game_players as its owner, and rewrites both SELECT policies to use it. Access is unchanged, and the domain_events and rooms policies work again as written.

pgTAP covers a seated player, an outsider, an anonymous client, AI seats, waiting and spectator-enabled games, and the function's definer and grant shape. The migration applies alone after 20260914000001, before or after 20260913000002. Roadmap section 1 drops the finished step and the release applies 20260914000001 and 20260914000002.
@verlyn13
verlyn13 merged commit 68ad950 into main Sep 14, 2026
8 checks passed
@verlyn13
verlyn13 deleted the fix/game-access-policies branch September 14, 2026 17:43
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