RowShield

Help / FAQ

Frequently asked questions

All plansLast reviewed 2026-08-23

Support answers cluster around a small set of concepts, so the most useful of them are collected here with self-contained answers: each one can be read on its own without hunting through other pages.

The questions fall into three groups. The first concerns reading posture claims — what the words on screen assert and where the numbers come from. The second concerns policy semantics that reliably catch people out: defaults, permissiveness and the ways a well-intentioned policy stops behaving as intended. The third concerns testing, and the gap between reading a table and exercising the rules that govern it.

If your question is not answered here, the troubleshooting section covers the operational cases in depth: connection failures, the meaning of empty responses, disagreements with the dashboard advisor, recursive-policy SQL errors, missing notifications and disconnection. Questions about scope, reporting and planned work live in the reference section.

Reading posture claims correctly

Every posture statement RowShield makes rests on two kinds of evidence, and knowing which backs a given claim tells you how much weight to give it. Behavioural evidence comes from the probe: an actual read attempted with the anon key, over the same public surface your users hit, with received rows counted and recorded. Configuration evidence comes from the catalogue: the row-security flag, the policy inventory, grants and function attributes, read at scan time and stamped with a timestamp.

Claims phrased as observed — rows readable, objects listable — rest on behaviour and are as strong as the network allowed them to be. Claims phrased as configured — policies present, row security enabled — rest on the catalogue and describe structure rather than outcome. Where the two sources agree, confidence is straightforward; where they diverge, behaviour governs questions of exposure and configuration governs questions of fragility, as the advisor-conflicts article explains in detail.

Timestamps matter too. Posture is a property of a moment, and a verdict earned yesterday describes yesterday's database. Continuous probing exists precisely because the database keeps changing underneath any static assessment.

Policy semantics that catch people out

Four behaviours account for most surprises. First, the default is denial: once row security is enabled on a table and no policy grants a role access, that role reads nothing, silently. Emptiness is therefore ambiguous, as the dedicated article explains.

Second, policies are permissive by default. Two permissive select policies on the same table combine with logical or, so adding a narrow policy alongside a broad one changes nothing for anyone covered by the broad one. Restrictive policies, joined with logical and, exist precisely to tighten, but they must be declared restrictive explicitly.

Third, the using clause filters the rows a caller can see, while with check governs the rows a caller may write. A table with a careful using clause and a careless with check can remain perfectly readable yet quietly accept writes that immediately become invisible to their own creator.

Fourth, policies bind to roles. A policy written for authenticated says nothing about anon, and the reverse holds equally. Roles added later inherit nothing; each needs its own consideration, which is why posture monitoring watches the role inventory and not just the policy list.

Testing beyond reading

Reading a table proves one fact about one moment. Testing policies means asserting the full matrix: this role may see these rows, may not see those, may not update the rest, and the boundaries hold for both the empty case and the populated case.

The practical recipe has three layers. In SQL, assume the role and supply representative claims, then run the queries and assert outcomes. In application tests, drive a real client against a staging database with tokens for each persona, asserting allowed and denied cases symmetrically — the denied half is where regressions hide. In continuous terms, let RowShield hold the anonymous edge: the probe re-exercises the outsider view on every scan, catching the migration that quietly dropped a policy weeks after the test suite last ran.

Keep the assertions close to the policies they exercise, and name them after the policy rather than after the feature, so a failing test points directly at the definition to reread. Treat the three layers as complements: local tests give fast, precise feedback; the probe gives continuity against change. Neither excuses the other.

Related questions

Does my app have RLS enabled?
Row level security is a per-table switch, so the honest answer is always per table. Check directly in SQL with select relrowsecurity from pg_class where relname = 'your_table'; a value of true means the switch is on for that table. The dashboard's table editor shows the same state as a toggle. RowShield reports it per table in every scan, and the posture view summarises the count of tables with the switch off, which is where exposure almost always begins.
Dashboard says protected, why is the table still readable?
Because a protected label describes configuration, and readability is behaviour. Policies can exist yet admit everyone — a predicate that collapses to true, a permissive policy inherited by a broad role — and views can carry rows past the table's policies entirely. The probe settles the question empirically: it reads with the anon key, exactly as a stranger would, and if rows come back they are readable regardless of what any panel asserts. Trust behaviour for exposure questions, then treat the label as a prompt to find which piece of configuration is more generous than it looks.
What is a tautological policy?
A tautological policy is one whose predicate is true for every row and every caller, however specific it looks in the editor. The obvious form is using (true). The dangerous forms are subtler: an expression comparing a value to itself, an or-chain with a constant branch, or a function call that always returns true for the roles involved. The effect is that the policy guards nothing while lending the appearance of protection — reviewers see a named policy and move on. RowShield's analysis flags predicates that hold universally so they can be rewritten as either an honest open grant or a real restriction.
Why do policies stop working over time?
Policies rarely rot on their own; the ground shifts beneath them. Migrations recreate tables and drop row security with them. New tables appear without any policy, inheriting default denial that a single wide grant then opens further than intended. Views get replaced and lose the invoker-security setting that kept table policies applying. Functions referenced inside predicates are edited and change their answers. New roles are added that no policy mentions. None of these events announces itself, which is why continuous probing exists: each scan re-tests the outsider view against the database as it is now, not as the last review left it.
What is schema drift?
Schema drift is the gap between the schema you believe you have — captured in migrations and review — and the schema that actually exists in the live database. It accumulates through console edits made under pressure, emergency fixes applied straight to production, environments rebuilt by hand after incidents, and branches merged without their database halves ever meeting. Drift matters to security because posture lives in the details: one recreated table, one missing policy, one widened grant. RowShield tracks the posture-relevant slice of drift — row security, policies, grants, exposed functions — and reports when the live database departs from what was last reviewed.
Is the anon key enough to read my tables?
Yes, whenever a table lacks row security or carries policies that admit it — and the anon key is public by design. It ships inside your frontend bundle, appears in browser network tabs and is visible to every visitor, so treating it as a secret provides no safety. The correct frame is that the anon key identifies the anonymous crowd, and row security defines what that crowd may see. This is exactly why the probe uses nothing but the anon key: it demonstrates, on every scan, what any stranger could do with the credentials you already publish.
Why does the same advisor warning keep coming back?
Because the condition it describes keeps coming true. Warnings recur for two ordinary reasons: the underlying configuration was never actually changed — the dismissal recorded an intention, not a fix — or it was changed and a later migration recreated the pattern, as happens when a regenerated table drops its policy. Dismissing a warning silences one instance, not the class. If a warning feels repetitive, that is the signal to fix the generating migration or convention rather than the instance, and RowShield keeps raising the finding for as long as the probe can demonstrate the condition holds.
How do I test policies rather than just read them?
Testing means asserting outcomes for roles and rows, both allowed and denied. In SQL, assume a role and supply representative claims — assume the authenticated role with sample JWT claims set — then assert what the queries return and reject. In application tests, drive a real client against staging with a token per persona and cover the denied cases symmetrically with the allowed ones. In continuous terms, RowShield holds the anonymous edge for you, re-exercising the outsider view on every scan so the migration that quietly weakens a policy is caught days later rather than at the next audit.

Did this answer your question? If not, tell us what is missing — article corrections go straight to the person who maintains it.

RowShield checks 9 rule classes continuously. This article describes shipped behaviour only.