Proposal: detect read policies whose USING is unconditionally true for unauthenticated sessions (inverted auth.* IS NULL disjunct)

Aperta
#165 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
45/100
Tipo di issue
Funzionalità
Chiarezza
Abbastanza chiara
Stato di attività
Tranquilla
Stack tecnologico
postgres

Direzione di ricerca

Inizia con lo skill /new-lint e il lint esistente 0024_rls_policy_always_true, quindi esamina il contratto standard dei lint e la struttura dei test pg_regress. Conferma il nuovo numero del lint e la scelta tra WARN/INFO prima di procedere. Il lavoro è completo quando viene rilevato il vero positivo proposto e i negativi elencati—current_user, IS NULL condizionato da AND, un predicato con ambito limitato e current_setting(name, false)—rimangono non rilevati nei test di regressione.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Hi! A suggestion for a new Security lint, related to but framed differently from PR #28 ("feat: add lint to detect RLS policies that allow access to anonymous users").

The gap

0024_rls_policy_always_true deliberately scopes its always-true USING check to command in ('UPDATE','DELETE','ALL') and matches only the literal forms ('true','(true)','1=1','(1=1)') — with the comment that SELECT … USING (true) is often intentional. That's the right call for literal true. But a very common real-world shape slips through entirely:

create policy "read own"
  on public.documents for select to authenticated
  using ( auth.uid() is null or owner_id = auth.uid() );

For any request where auth.uid() is NULL (an anonymous session, or any token-less call), auth.uid() is null is true, so the whole USING is true and every row is readable. It isn't literal-true, and it's a SELECT, so 0024 can't see it — yet it exposes the table.

Why this isn't the PR #28 discussion

This lint does not flag "the policy is reachable by anonymous users" — that's a deliberate design choice and not inherently a bug (which, as I read it, was the substance of the pushback on PR #28). It flags a predicate whose top-level OR short-circuits to true for any session where the auth function returns NULL — a logic error that is wrong whether or not anonymous sign-ins are enabled. (Which sessions those are depends on the policy's roles: for a public/anon policy it's any token-less request; for a TO authenticated policy it's the narrower case of an authenticated token with a NULL sub.) An … IS NULL OR … disjunct on an auth function almost always means the author intended the negation (IS NOT NULL AND …) or a scoped check, and accidentally turned the policy into a no-op. Framing it as a correctness defect (rather than an anonymous-access judgement) keeps it squarely in advisor territory.

Proposed detection

A pure SQL view over pg_policies, standard lint contract. It fires on a predicate shape, not on any project setting, so it needs no conditional execution:

  • permissive policy, cmd in ('SELECT','ALL'), granted to anon / authenticated / public;
  • normalized qual contains a top-level OR disjunct of the form <auth fn>() is null, where <auth fn> is genuinely nullable — auth.uid() / auth.jwt() / auth.role(), or current_setting('…', true).
  • Exclusions, to stay false-positive-clean: never-NULL identity functions (current_user / session_user / current_role / user) — those disjuncts are constant-false dead branches, not leaks; and current_setting(name) / current_setting(name, false), which raise rather than return NULL.
  • Severity: WARN/INFO; remediation: "remove the IS NULL disjunct, or replace it with an explicit deny."

A couple of questions before a PR

  1. Would you prefer a fresh lint number, or to revive/rework PR #28?
  2. Severity preference — WARN or INFO?

Happy to implement it with the /new-lint skill + pg_regress tests covering the true positive above plus negatives (current_user is null disjunct, AND-gated is null, a plain scoped predicate, current_setting(name, false) is null).

Lingua principale
PLpgSQL
Stelle
284
Fork
110
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di supabase/splinter

Tutte le issue di supabase/splinter

Issue simili

Altre issue su Databases

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.