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 FORCE | 2 — the policy is not applied |
with FORCE | 1 — 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.
Related
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.