Skip to content
Haven
← Notes from building HavenSAFETY

Why we test the impossible stays impossible

Access rules live in the database, not the interface. Proving the rules work is the easy half — the half that caught us out was proving the wrong caller still cannot reach them.

K
Keshav · · 4 min read

Where the rules actually live

Haven's access rules are in Postgres, not in React. Row level security is enabled table by table, and the policies on it decide what a request may read and write. The interface hides buttons too, but hiding a button is a courtesy to the person using the product, not a control — a control is something that still refuses when the button is gone.

That choice is what makes the rules testable at all. A rule enforced by an interface can only be tested through the interface, which means testing it as whoever the interface happens to be signing in as. A rule enforced by the database can be tested by asking the database directly, as each role in turn, including the roles nobody is supposed to be.

So the suite has two halves. One runs with no database at all and covers the decisions that are pure functions — which URLs are public, which documents a person is asked to accept, which courses reach a sitemap. The other runs against a real database carrying the migrations, and covers the parts only Postgres can answer. Neither half is quoted here as a count: a number of test files is the kind of figure that is wrong by the next change, which is the same reason this post is not called “216 tests” any more.

The failure that taught us the difference

In September a migration revoked EXECUTE on the function that activates a paid enrolment. It revoked it from `public` — the default grant every role inherits — intending to close the function to clients. It closed it to the service role at the same time.

Every paid enrolment on Haven then failed at the final step. Stripe took the payment, the webhook fired, and the last call returned a permission error instead of creating the place. That lasted sixteen days.

The test suite was green the entire time. There was already a suite covering that function, and it was a good one: it proved the function creates the enrolment, links the payment, and stays idempotent when Stripe redelivers the same event. It proved all of that as `postgres`, which is the one role that could always call it.

The question the suite never asked was not whether the body worked. It was whether the caller could reach the body.

What we changed

There is now a suite whose entire job is the privilege boundary on that function: the service role in, clients out, asserted in both directions so neither half can drift without failing.

It carries no fixtures, deliberately. Calling with an identifier that matches no payment separates the two outcomes exactly — a permission error means the caller never entered the function, and a no-data error means the caller entered it and the body raised on the row that is not there. One call, and the answer distinguishes a closed door from an empty room.

The same shape now covers the parts of Haven with the most authorisation behind them. Chat has seventeen functions that are the only way to write to conversations, messages, blocks and reports, because none of those tables grants a client insert. One missing grant and Chat stops working for everybody; one extra and it works for the wrong people. So its access control list is asserted twice — as the catalogue records it, and as Postgres enforces it — and then the behaviour is exercised against real rows.

How the database suites are built

Each SQL suite runs in one transaction that ends in a rollback, so it is safe to run against a database carrying real conversations.

The fixtures are made through the product's own gates rather than around them. Nothing is inserted with triggers disabled, so the rules that decide a course starts as a draft, that publishing requires an approved and verified teacher, and that an enrolment has preconditions all have to be satisfied the way the application satisfies them. A fixture built by switching the rules off would prove the rules work on data that could never exist.

Several suites take a baseline count before they start and compare against it at the end, so a suite that touched something it should not have says so rather than passing quietly.

What this does not prove

A green suite is not a security audit. Haven has not had one. These tests prove that specific things we thought of stay true; they say nothing about the things we did not think of, and the gap between those two sets is where real incidents live.

The enrolment failure is the honest example. Nothing was wrong with the tests we had. What was wrong was the question they were asking, and no amount of running them harder would have surfaced it.

The rule we work to now is narrow enough to be useful: when a change decides who may do something, the test has to be written from the position of somebody who may not.

More from Haven

How the product works, and the safety decisions behind it.

Haven uses Google Analytics to understand which courses people find and where they get stuck. It sets a cookie and never receives your name, email or payment details. Declining keeps everything on the site working. Privacy Policy