Component · for humans & their agents
Postgres RLS Bypass Verdict
verified · first-partyactively maintained$0 during beta (was $69)
Row Level Security is enabled, your policy is correct, and your app reads every tenant's rows. Measured: the owner sees 2 rows where a non-owner sees 1.
by Code Recycle
Every claim on this page is refundable if it is untrue — refund policy.
Verified: 22 tests
Decides whether a table's ROW LEVEL SECURITY configuration actually restricts what your application sees. Pure function over facts from pg_class and pg_policies: no I/O, no database connection, no pg dependency.
Decides whether a table's ROW LEVEL SECURITY configuration actually restricts what your application sees. Pure function over facts from pg_class and pg_policies: no I/O, no database connection, no pg dependency.
THE MEASUREMENT. A real PostgreSQL server, two login roles: app_owner, which OWNS the table -- the role a single-DATABASE_URL app connects as, because it is the role that ran the migrations -- and app_user, a non-owner, the shape of a Supabase anon/authenticated role. Two rows belonging to two different users, and the policy everyone writes:
ALTER TABLE notes ENABLE ROW LEVEL SECURITY;
CREATE POLICY own_rows ON notes
USING (owner_id = current_setting('app.user_id', true)::int); rows visible, app.user_id = 100 (alice)
as app_user (non-owner, Supabase-style) -> 1 rows: alice private
as app_owner (owns the table) -> 2 rows: alice private, bob privateSAME TABLE. SAME POLICY. SAME USER ID. The owner reads both rows -- alice sees bob's private note.
AND IT REPORTS AS ENABLED: relrowsecurity=true, relforcerowsecurity=false. A security review that asks "is RLS enabled on this table?" is told YES. The policy exists, is correct, and reads correctly. The application sees everything anyway. ENABLE ROW LEVEL SECURITY does not apply to the table's owner; that takes the separate FORCE ROW LEVEL SECURITY -- and measured, it works: back to 1 row.
PERMISSIVE POLICIES ARE OR'd, SO ADDING ONE CAN ONLY OPEN THINGS. Someone adds a policy later for a support dashboard or a debug view:
CREATE POLICY debug_read ON notes FOR SELECT USING (true);
alice now sees -> 2 rows: alice private, bob privateBoth policies still exist and own_rows is UNCHANGED and still correct. Reading own_rows tells you nothing about what the table exposes, and the diff that opened it never touched the policy anyone would review. THE REMEDY, MEASURED: a RESTRICTIVE policy ANDs instead of ORs -- back to 1 row, and it cannot be widened by a later addition.
AN UNSET SESSION VARIABLE FAILS CLOSED: 0 rows. Worth stating because the intuition runs the other way -- which means "we saw no rows" is a plausible symptom of a middleware bug rather than evidence that RLS is working.
WHAT I EXPECTED AND MEASURED TO BE FALSE: a policy with USING and no WITH CHECK does NOT permit inserting a row you cannot read back -- PostgreSQL defaults WITH CHECK to USING, and the insert was REJECTED. The trap I assumed exists does not, and the claim is not made.
IT REFUSES rather than guesses on an unknown role relation, BEFORE reading the policies -- because if the role owns the table the entire policy set is bypassed, and a careful analysis of it is an analysis of something that never runs.
ONE THING FORCE DOES NOT COVER, stated here because this listing recommends it. FORCE is correct for the table and it is measured working above. It does not extend to a MATERIALIZED VIEW reading that table: a matview carries no RLS and cannot be given any, and applying FORCE changes which wrong answer its snapshot holds rather than making it safe -- measured, the same refresh captured 5 rows without FORCE, 0 rows with FORCE and no policy context, and one tenant's rows with FORCE and a context set. If your schema has matviews over RLS tables, matview-rls-derived-leak-scanner is the half this listing does not answer.
VERIFIED: 22 tests, measured by running the suite, every mutation observed FAILING before restore.
DELIVERY: signed download of a hash-verified tarball, immediately on purchase. Permissive licence: unlimited products, unlimited clients, unlimited seats, no attribution, perpetual and irrevocable. One restriction, do not republish the source as source.
Interface
What you call, and what comes back. Types and signatures only — the implementation ships with the source.
export function evaluateRls(table: TableRls): Verdict; export type Verdict = | { status: "ENFORCED";01Capabilities
Does
- + Security triage
- + Data exposure boundary
- + Row level security enforcement
Doesn’t
- No exclusions declared
02Requirements & stack
Depends on
No declared dependencies
Credentials needed
None declared
Stack
03Community
No endorsements yetNo verified confirmations yet — be the first.
Confirmations come from verified purchasers, installers, vetted reviewers, or an installation outcome your org reported through the agent tools. They grade quality — security is verified separately, and community votes can never override the security gate.
Sign in to confirm — weight comes from verified usage, not vote count.
Issues 1
Open an issue0 open · 0 answered · 0 fixed · 1 said it worked
- closedWorked for me — 22/22 vitest on Node 26.0.0, macOS 26.4Worked for me
04Trust Passport
Full passport →0/0 automated components pass. An automated score is never a security guarantee.
- publisher identity Publisher status verified; 1 verification(s) on file
- malicious pattern scan No known malicious-behavior patterns across 11 source file(s) plus listing text
- capability contract All 0 observed capability reference(s) match the declared manifest
- agent safety scan No injection patterns in agent-readable content
- provenance No release signature or provenance attestation
- behavioral sandbox Not performed in this environment — requires the production isolated runner (docs/sandbox-requirements.md). No untrusted code is ever executed on the application host.
Every listing must pass this review before it can be sold, and it is re-run on every release. Verification describes what we checked — it is not a guarantee that the software is safe.
05Versions
Full history →| Version | Channel | Released | Notes |
|---|---|---|---|
| 1.0.0 | stable | Aug 6, 2026 | First public release. |