An empty response has three possible meanings
The probe reads your tables exactly the way a stranger would: with the anon key, over the public REST surface.
When the response comes back as a success code with an empty array, the silence is ambiguous, and treating it as good news is the most common misreading of a scan. This article sets out the three distinct situations an empty response can represent, how RowShield separates them, and why an empty array is reported as reachable rather than as proof that nothing leaks.
Denied by default
Row level security is enabled and no policy grants the anon role permission to select. Postgres does not raise an error when a policy filters every row away; it quietly returns an empty set, and the REST layer wraps that as a success carrying no records. From the client's point of view this is indistinguishable from a table holding nothing at all.
This is the healthy case. Your data is present, the door is shut, and the emptiness is the lock doing its work. RowShield confirms it by consulting the catalogue at scan time: the row-security flag is set, the policy inventory contains no entry that admits the anon role, and the grant matrix adds nothing further. The verdict records a denied-by-default posture, which is the strongest quiet outcome available.
Genuinely empty, and filtered by policy
The second meaning is mundane: the policies permit reading and the table simply holds no rows. Staging databases that were reset, tables awaiting their first insert and partitions carrying no data all produce the same empty array as a locked table. Nothing about the response itself tells you which world you are in.
The third meaning sits between the two. A policy allows a subset of rows — published entries only, or records belonging to their creator — and every row currently in the table fails that predicate. Anonymous visitors see nothing, authenticated users see plenty, and the empty array reflects the predicate rather than an empty store or a closed door.
RowShield separates all three by combining the behavioural probe with configuration facts: whether row security is enabled, which policies exist, and what those policies admit. The verdict names the situation rather than leaving you to infer it from an empty bracket pair.
Why 200 with an empty array is reported as reachable, not safe
A success code with an empty body proves exactly one thing: the endpoint answered and the round trip completed. It does not prove the table is protected, because protection and emptiness are unrelated properties. A table can be empty today and leaking tomorrow, the moment the first row is inserted into an unprotected store.
That is why such tables are reported as reachable, never as clean. Reachability is the claim the evidence supports; leak findings require rows actually observed crossing the boundary. This asymmetry protects you in both directions: it avoids false reassurance today, and it avoids crying wolf about tables that merely happen to be vacant. If a reachable-and-unprotected table receives data later, the next scan converts the verdict into a leak finding automatically.
Did this answer your question? If not, tell us what is missing — article corrections go straight to the person who maintains it.