Thirty-five tables, no policies, and nobody looking
The question
What is actually reachable with the public API key of a database I have been treating as private?
What I assumed going in
That row-level security was on, because I had been enabling it per table as I added them. I was not auditing. I was adding an unrelated table and happened to look.
What I actually did
The data API was enabled and all 35 public tables were granted to the anonymous and authenticated roles with zero policies. Anyone holding the public key could read, modify or truncate production — including the table holding one-time sign-in codes, which is an authentication bypass rather than a data leak.
The field-level encryption on identifiers, licences and bank details was the only layer that had been doing its job.
What I learned
Closed the same day by disabling the data API, after verifying that nothing in the application used it. Then wrote a hardening script as a guard against it being turned back on.
The real finding is not the misconfiguration. It is that the control was my memory. Enabling security per table, by hand, as you go, works exactly as long as you never forget — and the failure mode is silent, undated and indistinguishable from success.
The later platform does it differently: every table gets row-level security, a restrictive no-access policy and no grants, and a database trigger locks each new table the moment it is created. There is nothing to remember.
What is still uncertain
Whether the trigger approach survives a migration tool that creates tables in a way the trigger does not see. I have not tried to break it on purpose.
What I would test next
Write a test that creates a table through each path a migration might take, then asserts from outside — using the public key, as an attacker would — that it answers nothing.
Found by accident while doing something else, which is the only reason it was found at all.