Row-level security scopes rows. It says nothing about columns. I found this in my own live produc...Row-level security scopes rows. It says nothing about columns. I found this in my own live produc...
The network for creativity
Join 1.25M professional creatives like you
Connect with clients, get discovered, and run your business 100% commission-free
Creatives on Contra have earned over $150M and we are just getting started
Row-level security scopes rows. It says nothing about columns.
I found this in my own live product, whiI had RLS policies on every tenant table. I had reviewed them. They were correct: a user may update the row whose id matches their own, nothing eThen I tried it from the browser console. Update my own row, set plan tIt worked.
Not a bug in the policy. The policy did exactly what it said. The row genuinely belonged to me, the check genuinely passed, and I got a free upgrade. The same class of write let me grant myselfThe mistake was assuming RLS could express something it has no vocabularThe fix was three layers.
RLS with FORCE enabled, because Postgres exempts a table's owner from its own policies unless you force it. ThaA before-update trigger that compares old to new and rejects writes to plan, role and tenant columns unless the caller is an authorized internMetering moved into security-definer functions, so the client supplies no count and cannot reach the counter.
Then fifteen integration tests. Each one performs the attack and asserts the database refuses it. Most auth suites cIf you run multi-tenant Postgres, the question worth asking tonight is not whether your policies are correct. It is whether the thing you are relying on caFull write-up and the repo are on my profile.Anyone else hit this one?
n express the rule you think you wrote.
onfirm that permitted actions succeed, which is the less useful half, because a policy that is too permissive passes every happy path test you can write.
al writer.
t one is silent. No error, no warning, and your system looks fine while it is wide open.
y for. It answers which rows. It says nothing about which columns of those rows.
admin, reset my usage counter, and move myself into a different tenant.
o pro.
lse.
ch is the part that stings.
Back to feed
The network for creativity
Join 1.25M professional creatives like you
Connect with clients, get discovered, and run your business 100% commission-free
Creatives on Contra have earned over $150M and we are just getting started