Skip to content
← Back to blog

Published on October 7, 2026 · 4 min read

Three ways of checking the security lied. The fourth couldn't

In my panel, every entry point defends itself. There's no top-level layer that lets traffic through or blocks it — there's a single session-check line, repeated separately in every file. Forgetting it opens an entry point with no symptom at all: the code compiles, the tests pass, the panel works.

On 6 August 2026 I counted how many of these entry points there really are, and which of them refuse. The result: 266 HTTP handlers across 188 files. What's interesting isn't the number — it's that the first three ways of counting it gave the wrong answer.

Three methods, three lies

Searching by file. A file can have four entry points and one session check. In the previous audit, this method counted nine public routes. There were actually sixteen public entry points, across fourteen files — five of those files mentioned the session check in a comment, so the search counted them as protected. The result looked like a result.

Searching by individual entry point. Better, but protection sometimes sits one call further away — the entry point calls a function, and the check lives inside that function. This method reported 21 holes, of which zero were real.

Counting files instead of entry points. A mistake I made myself, in a document warning against the first method: I wrote "eight public pages," and there are actually ten handlers. The same mistake, in a sentence cautioning against it.

A probe instead of reading

Only live firing settled it. I ran the system locally, disabled the developer login bypass, and sent a request to each of the 266 entry points — no session cookie, no authentication header at all.

252 refused. 14 responded, each with a named mechanism guarding it instead of a session: a contact form with a rate limit on submissions, data that is public by definition, documents opened via a link where a token plays the role of a password.

This is where the real part begins.

A check that only sees refusals can be broken

A probe that answers "refused" to everything looks identical whether the system is airtight or the probe itself isn't working. Green across the whole list is not proof — it's an absence of information.

So I looked for proof in its own results: one entry point returned full data, another rejected the request because of a badly filled form. If the probe were broken, both of those would also have shown a refusal. The fact that it distinguishes three different answers is the proof that the refusals at the other 252 spots mean something.

I caught a second variant of the same mistake halfway through the work. Four routes are protected not by a session but by a secret — and the first pass sent them the secret in the wrong place: one in the URL parameter instead of the header, another in the request body instead of the parameter. Both refused, and it looked like correct protection. A refusal caused by asking the question wrong is indistinguishable from a refusal caused by protection. Only showing that these routes let you in with the correct secret closes the case both ways.

Along the way, a third scheduled job turned up that wasn't on any list of mine. I didn't find it by reading — it found itself, because the probe queried everything.

What I did not do

I did not add a session check to the fourteen public entry points. That was the most tempting thing all day: fourteen unprotected spots looks like fourteen things to fix, and a change like that looks like a security patch in the project's history. It would have broken the product — real customers, who don't have and won't have accounts, go through those entry points.

I did not touch the rate-limit thresholds, not even for the duration of the measurement. Tempting, because a probe firing 266 times can burn through it. Instead I arranged the probe so its second pass skips the public routes. A measurement that loosens the thing being measured for its own convenience measures something other than the product.

I did not write that the probe proves more than it proves. It substitutes nonexistent identifiers, so a route protected only for certain values would still look protected. I read the highest-stakes entry points a second time, by hand.

Three things I take from this

A check that cannot deliver bad news is not a check. Before you trust a green result, show that the same mechanism is capable of lighting up red.

A refusal can be a symptom of brokenness, not protection. A locked door and a door you knocked on in the wrong wall look identical from the outside.

An empty result is only worth as much as the method that produced it. Zero holes is good news only if the path to that zero can be repeated — which is why the probe lives in the repository as a script, and refuses to run while the login bypass is enabled.


The numbers — 266 entry points, 252 refusals, 14 deliberately public, 21 false alarms from the previous method — come from a single measurement on 6 August 2026, run against a local copy of my system with a test database. I did not query production.

Patryk Piecyk

Patryk Piecyk

Warsaw · employed since 10.2026 · growing towards ERP/SAP and AI

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 →