A security PR landed in Freebo last week. Fifty-six files, 3,541 insertions, 326 deletions. The commit message said “xss + tenant boundaries” and the diff was, generously, a book.
I did not write it. I read it. That distinction is the whole point of this post.
0
files touched
0
insertions
0
deletions
0
RLS migration
I run Freebo as a solo founder. Claude Code writes most of the code. I do not want to be the founder who signs off on a merged security PR by squinting at the diff and nodding. I want to actually understand what changed, layer by layer, so that six months from now when a customer asks “can a manager suspend an admin?” I can answer without opening the file.
So I have a routine. Here is exactly what I do.
Step one: ask for the shape, not the code
The first prompt is always the same. Some variation of:
Explain what this PR actually did. How many lines,
where is the bulk of the change, and what are the
three or four layers it touches. Do not paste code.
Do not explain how permissions work. Just tell me
the shape.
The point of that prompt is to prevent the thing that always kills my attention on a big PR — being handed a wall of code and told “here is what changed.” I do not care yet. I want the map first.
For this PR the answer came back roughly as: four layers. Supabase migration and RLS (one file, small). API middleware and permission service (two files, medium, hot). Route handlers (about ten files, small changes that all reference the same new helper). Frontend auth store and API client (three files, mostly hardening). Plus a lot of tests. Roughly three thousand of the new lines are tests.
That last number is the one I care most about. If a security PR is 85% tests, I trust it more than one that is 85% new code.
Step two: the one-file-per-layer read
Once I have the shape, I pick one file per layer and read it — with Claude walking me through it inline, line by line, no summaries. The goal is not to memorize the file. The goal is to have one concrete example in each layer that I understood end to end.
For this PR the four files were:
permission.service.ts— the new ceiling logicusers/index.ts— the route that enforces itauthStore.ts— how the frontend deals with a 403..._restrict_invitation_tokens_to_service_role.sql— the migration
I do not read every file that was touched. I read one per layer, deeply, and then I ask Claude “is anything in the other files fundamentally different from what I just read, or is it the same pattern applied elsewhere?” Nine times out of ten the answer is “same pattern.” The one time it is not, that is the file I read next.
Step three: find the one load-bearing line
Every non-trivial PR has one line that carries the whole idea. If I can find it and understand why it is written the way it is, I have understood the PR. If I cannot find it, I have not.
For this one, the load-bearing line is a function called holdsInFull. Its job is to answer a single question: does the person doing the granting have the permission they are trying to grant?
Before this PR: a manager with users:manage could invite an admin, demote an admin, or grant a permission the manager themselves did not hold. That is the kind of bug that does not blow up on day one. It blows up on month six when an operator promotes a seasonal guide to manager and the manager quietly promotes themselves to admin over a slow lunch shift.
The fix is small and mean. The commit message says it best:
Nobody can grant or take authority they don’t hold.
— the commit message, which I read three times before opening the file
The mechanics of that sentence turn into a rule with two halves. When granting: every permission you assign — including every explicit true in the custom overrides — must be one the actor holds in full. When acting on another member — role change, suspend, restore, remove — the target’s permissions must not exceed the actor’s. A wildcard like * or users:* counts as “held in full” only when the actor has no explicit false denial carving something out of it.
That last clause is the one I could not have written on my best day. The idea that a wildcard is only a wildcard when nothing beneath it has been explicitly denied is exactly the kind of edge case a real attacker would find at 3am and I would find in a support ticket.

Step four: ask what would still break
The last thing I do — always — is turn the review around. I ask:
If I were trying to defeat this ceiling, what would
I try? What is the closest that a users:manage
manager can still legally get to admin-level power?
That is where I learned that ordinary role permissions are deliberately not compared. A manager can still assign a crew role that carries waivers:read even though the manager themselves lacks that permission. Not because it is a hole — because those are non-authority permissions. Waivers-read does not let you grant waivers-read to anyone else, and it does not let you touch other members. So it is fine that a manager can hand it out.
Understanding why the ceiling stops where it stops is the actual point of the review. Anyone can read a diff. Understanding the shape of what was deliberately left permissive is how you tell whether you have a security posture or just a security patch.
What actually changed, in one sentence per layer
Because I keep a note like this after every big PR I read — this is the whole payoff of the routine:
- DB. One migration locks invitation tokens to the service role so nobody can query pending invites through the anon key.
- API. A ceiling in the permission service refuses any grant or member-action where the actor does not hold, in full, the authority being touched — with wildcards checked against explicit denials.
- Middleware. The route layer stays the same shape. The ceiling is a second check that runs after the route permission passes.
- Frontend. The auth store and API client handle 403s from the new ceiling cleanly. No accidental logout on a merely-forbidden action.
That is the whole PR. Fifty-six files. Four sentences.
The takeaway
If you are a founder shipping serious software written mostly by an agent, the skill that matters is not “can you write the code.” It is “can you read the code you did not write, at a depth that survives contact with a real user six months later.”
The routine above is not clever. It is boring. Shape first, one file per layer, find the load-bearing line, ask what still breaks. It works because it forces the AI to explain why the change is shaped the way it is, and because it forces you — the founder — to write down the answer in your own words.
Do that on every big PR, and by the third one you will notice that you are asking sharper questions than you were on the first. That is the whole game. Not writing more code. Understanding more of the code that ships with your name on it.