Published on August 26, 2026 · 3 min read
I counted how many of my API routes are really protected. Grepping by file lied
My panel has over 240 API routes today. Every one of them starts with the same line:
if (!(await isAuthed())) return new Response(null, { status: 401 });
There is no layer doing this for me. In this framework the /api directory sits outside the middleware's scope, so the whole of access control is that one line, repeated in every file separately. Forgetting it produces no symptom: the code compiles, the panel works, and the route stands open to anyone who knows the address.
I decided to count how many of them there really are.
The first answer was false
The reflex: count the files containing isAuthed and compare with the number of route files. It came out nicely — almost all of them.
Almost. I started looking through the ones making up that "almost", and ran into this:
// PUBLIC route — deliberately without isAuthed(): the client opens it
// from a link in an email, so there is no session here to check.
The file mentioned isAuthed. In a comment. Explaining why it is not there.
There were five such places. My first count scored every one of them as "protected", because I was searching for a string in a file instead of answering the question I actually cared about.
The second mistake: a file is not a route
Even after throwing out comments, counting by file makes no sense, because one file can serve several HTTP methods. A typical route looks like this:
export async function GET() { … }
export async function PATCH(){ … }
export async function DELETE(){ … }
If the check sits in GET, but someone later added DELETE and forgot it — the file still "contains isAuthed". The search says: protected. Reality: anyone can delete the record.
The right unit of counting is the HTTP handler, not the file. Only that arithmetic answers the question "is every entry into the system closed".
How I counted it in the end
I listed every exported GET/POST/PATCH/PUT/DELETE function in the routes directory, and then for each one checked whether the session check falls inside its body, not somewhere in the file. Plus a separate list of routes that are public by design — a client accepting an offer, picking up a document from a link, the contact form — each with a short note on why it is allowed to stand open.
The result is boring, and that is the point: two lists you can read through in five minutes, and the question "how many routes are unprotected" now has a number for an answer instead of an impression.
What follows more generally
A text search answers a question about text, not about behaviour. That sounds trivial and is exactly why it is dangerous: the result looks like a measurement while being nothing more than a hit count. Every time I draw a conclusion from such a search, I have to ask myself whether the thing I counted is the thing I asked about.
Security built on repeating the same line is security built on memory. In the long run this should be a barrier you cannot walk through by forgetting. For today I have a list and a test that checks it — knowingly less than a barrier, but a good deal more than hope.
Public can be correct — provided it is written down. Five routes stand open on purpose. The difference between a deliberate exception and a hole is solely that somebody recorded which is which. An undocumented exception is, six months later, indistinguishable from a mistake — even to its author.
This was part of a review I ran on my own system, by myself, with no outside auditor. I am not claiming it is complete — I am claiming that after it I know how many routes I have and which of them are open, and that before it I merely had an impression.

Patryk Piecyk
Warsaw · junior implementation consultant · available now
For seven and a half years I worked at a German company, five of them running its office: orders, invoices, complaints, ERP. Since June 2026 I have been building my own tools for that same work — I am not a programmer by training; the code is written together with an AI assistant, while the design, the decisions and the testing are mine. These notes describe what broke in those systems and what came out of it.
Got a question about this piece?
Write to me →