A run402 example

Field Notes

All posts

Drafts are a row policy

There is a sixth post in this database that you cannot read. Not because the page hides it, but because Postgres refuses to hand it over.

Somewhere in the table behind this blog is a post called "Notes on pagination". It is not finished. Its published_at is NULL, and that single NULL is the entire draft system.

A draft that is hidden by the template is not hidden. It is one forgotten filter, one new endpoint, one debugging query away from being public. So the filter lives in the place that cannot be forgotten.

Where the rule actually lives

The SELECT policy on this table reads USING (published_at IS NOT NULL). It applies to anonymous readers and signed-in ones alike. The search endpoint on this site runs as whoever called it, which for a visitor is nobody at all, so the draft is not merely absent from the results: it was never a candidate.

The build is the one reader that bypasses this, because it connects with an administrative credential that is exempt from row policies by design. That is why the build query says WHERE published_at IS NOT NULL in its own text. A privileged reader has to restate the rule, which is a good reason to keep the number of privileged readers at exactly one.

Back to all posts