Skip to main content

SEC.RLS.NOT_FORCED — Your policies do not apply to your own connection

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

The everyday Laravel arrangement is the dangerous one

ENABLE ROW LEVEL SECURITY exempts the table's owner. A Laravel application usually has one connection in config/database.php, used by migrations and by requests alike, and the role behind it owns the tables it created. Every policy on the database is then bypassed by the application itself.

Measured against PostgreSQL 18.4 — two rows, RLS on, USING (tenant_id = 1), connecting as the owner:

Rows returned
without FORCE2 — the policy is not applied
with FORCE1 — the policy is applied

Nothing in the policy definitions says this. Reviewing them comes back correct.

SQLens reports this rule when the role it audited as is the table's owner, or is a member of the owner role — PostgreSQL applies the exemption to a role holding the owner's rights through membership, which is how most deploys are arranged (app a member of app_owner).

Bad

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant')::uuid);
-- …and the application connects as the role that owns `orders`

Good

ALTER TABLE orders FORCE ROW LEVEL SECURITY;

Expect the application to start returning fewer rows after this — that is the policy taking effect for the first time. Set app.tenant per request before you deploy it.

SEC.RLS.OWNER_UNRESTRICTED is the same missing FORCE reported at medium, for a table this connection does not own. FORCE does not affect an account with BYPASSRLS: where SQLens sees one, this finding names it and points at SEC.PRIV.ROLE_BYPASSRLS.