Skip to main content

SEC.RLS.POLICY_ALWAYS_TRUE — A policy that lets every row through

  • Category: security
  • Severity: high
  • Level: 0
  • Confidence: deterministic
  • Downtime class: none — the finding is about a policy, not about a statement
  • Stability: stable
  • Suites: audit
  • Applies to: PostgreSQL 18

The state that passes every review

The table has row-level security enabled. It has a policy. \d+ shows both. And every row is visible to everybody, because the policy's filter is a constant that is always true.

USING (true) is what somebody writes to get a feature working, intending to come back to it. Nothing afterwards looks wrong.

One always-true policy is enough

Permissive policies are OR-ed. A table with a careful tenant policy and one USING (true) policy is exactly as exposed as a table with only the second — the careful one contributes nothing to the outcome. So the finding names the offending policy: that is the one to open, and fixing the other one changes nothing.

A restrictive policy cannot cause this. It is AND-ed and can only narrow, so SQLens does not count one.

What SQLens will not decide

It recognizes the constant forms PostgreSQL deparses an always-true filter into — measured against the server, not guessed:

You writeThe catalog stores
USING ( TRUE )true
USING ((true))true
USING ('t'::bool)true
USING (1 = 1)(1 = 1)

Anything with a function call, a current_setting() comparison or a subquery is a filter SQLens does not evaluate — including expressions that happen to be always true, such as (1 = 2) OR (3 > 2). It says nothing about those rather than guessing. A rule that tried to decide whether an arbitrary expression can ever be false would be wrong occasionally and confident always, which in a security suite is worse than being silent.

Bad

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders USING (true); -- placeholder that shipped

Good

CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant')::uuid);

SEC.RLS.DISABLED covers the table with no row-level security at all; SEC.RLS.CHECK_ALWAYS_TRUE covers the narrower case where only the write path is unguarded.