Presence check versus a completeness check

AI Security Checks
Most code review asks one question: does this look right? A security review that only asks that question will miss
  • a debug flag sitting where a config value should be,
  • a validation function that exists in the codebase but never gets called,
  • or a mitigation that only handles the obvious path.
The alternative is to ask a different question: what would someone trying to break in find? That’s the premise of a persona-based security audit — read the codebase the way an attacker would, not the way a reviewer skimming for style issues would.
We applied that approach to a legacy PHP platform over four weeks in early 2026. It surfaced five findings that a normal review had missed.

Why ask the adversarial question at all

A persona audit doesn’t wait for an external party to ask, “What’s wrong with this system?” Instead, it asks that question first. The reviewer takes on the role of an attacker rather than a maintainer and works through the codebase with that mindset.
The output gets prioritised Critical, High, Medium, Low, and the remediation follows that order: public-facing, unauthenticated code first, admin-only code further down the list, regardless of how many individual issues each category contains.
The five findings below all came out of that same audit, run against the same codebase, in the same review window. They’re grouped here because they share a structure, not because they were unusual to find. A methodical adversarial read finds this class of issue reliably, in any codebase old enough to have accumulated a few years of feature work.

Five things a presence check misses

**The debug flag.**

A single line, `var blTesting = true`, sat in the AJAX layer logging the current user’s ID, session key, and full API parameters to the browser console on every authenticated request. It read like a leftover from a debugging session — because it was one — and it caused no visible problem: pages loaded, sessions worked, nothing appeared in an error log.
The same boolean that enables logging also disables privacy. Code that helps a developer understand the system can help an attacker understand it too.
The audit’s fix took five minutes. Finding it took asking the right question.

**The shared variable.**

A discussion-comment field was rendered into HTML at three separate locations across the discussion module, none of them escaping it. Different developers wrote each render path at different times. None looked wrong in isolation.
Together, they shared the same assumption: that data read from the database was already safe to print.
The database is not a trust boundary. Finding one unescaped render point means checking every other place the same variable flows to.

**The trusted header.**

The function that identified a request’s IP address checked a client-supplied header before the connection address because it tried to support proxy traffic.
Any client could set that header to anything, which meant every per-IP rate limit in the system — login throttling, registration limits — could be reset by adding one line to a request.
The function’s name implied it was reliable. Its priority order wasn’t.

**The token that was read, not checked.**

An endpoint pulled a CSRF token out of the POST body — the same variable name, the same pattern used correctly elsewhere in the codebase — but never passed it to the validation function that actually checks it.
At first glance, extracting the token looks like protection. A reviewer only confirms the control by checking whether the code validates the token instead of simply reading it
A variable name can suggest a security control without actually providing one. That makes it more misleading than having no control at all because it passes a quick scan without adding any defence.

**The cookie holding a query.**

A SQL fragment defining how records were filtered in one part of the platform was persisted client-side in a cookie, to avoid a database round-trip on every page load.
The immediate fix was to HMAC-sign the cookie so the application rejected tampered values.
It didn’t change the underlying model: a client-controlled value was standing in for server-side state, and the signature only holds for as long as it stays unbroken. A remediation is not the same as a redesign.

Why this shape of bug is common, not exceptional

None of these five require a careless developer or an under-resourced team. Debug flags get added to solve a real problem and outlive the problem they solved. Escaping gets applied inconsistently across files written months apart. A function’s trust priority looks reasonable until someone asks which of its inputs are actually attacker-controlled.
These are structural properties of software that evolves over time. They appear in codebases maintained by one developer or fifty.
What differs is whether anyone goes looking for them this way. A presence check — is there a debug flag, is there escaping, is there a CSRF token — passes all five of these. A completeness check — does the flag ever get read in production, is every render path escaped, does the token actually get validated — catches all five. The gap between those two questions is where this entire list lives.

Remediation as maintenance, not crisis

The team didn’t treat any of these five as emergencies.
The team logged and prioritised them alongside a longer list from the same audit — a MyISAM-to-InnoDB migration, session cookie hardening, SQL injection remediation across ten batches — and worked through in parallel with ordinary development.
Security is not a milestone a system reaches and then stops needing; it’s maintenance, done on a schedule, against a list that gets checked off in order of what an attacker would reach first.

What’s next

An external penetration test is scheduled for Q3 2026 — the next test of the review process itself, not just the code it reviewed. There’s a real difference between “we checked” and “we were tested,” and it’s the difference an audit trail like this one is supposed to survive.
The internal audit found these issues before anyone outside the project looked for them.  Whether the next round finds anything the internal process missed is the actual measure of whether the process works.
The measure of a security practice isn’t whether bugs existed — every system old enough to matter has them. It’s whether a deliberate process exists to find the ones that look fine on a normal read, before someone with the wrong intentions finds them first.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.