Where AI belongs in a penetration test

Siemba · 2025

Siemba sells penetration testing to companies whose reports have to survive an audit. Every engagement started with a human tester doing the same opening moves, and genpt came out of asking which of those moves needed the human. I built it with two other backend engineers and wrote the design document the rest was built from.

63% less manual testing effort

siemba.com/genpthttps://example.comA web app. There is a login on it.Username and password?•••••••• ••••••••In. Running six tools against it.https://example.comStartexample.comscan complete5findings1high2medium2lowReflected XSS on the search parameterhighVerbose error page reveals the stackmediumSession cookie without SameSitemediumMissing HSTS headerlowDirectory listing enabledlowan address, two questions, and a report at the end of it
An address, two questions about the target, and findings back. Everything on this screen is invented; nothing of the real interface is reproduced.

Which parts actually need a person

A tester starting on a new application does a predictable sequence. Work out what they are looking at, get past the login, point the right tools at it, then read what comes back and decide what is real and how bad it is. Only a fraction of that is a judgment call. Most of it is knowing which thing to run next, which is knowledge rather than thinking.

That distinction became the whole design. Knowledge you can write down, so it goes in a table. Judgment you cannot, and that is the only place a model is worth its cost and its unpredictability.

It is worth being clear about what that buys. A table is auditable, cheap, instant, and gives the same answer on Tuesday that it gave on Monday. A model is none of those things. Every step we could move out of the second category and into the first made the system easier to reason about and cheaper to run, so the design pressure ran in one direction the whole way through: use the model in fewer places, and make those places count.

What happens when you paste a URL

You give it an address and answer a few questions about what the target is and how to authenticate. It works out which tools apply, runs them, and returns findings with a severity, a score and remediation text. Underneath it is a relay rather than one system: a wizard collecting setup, a queue carrying work, tools running in isolation for that scan, results coming back to be made readable.

The setup reads as a conversation because it genuinely is one. Answers do not come back in the response to the question. The request returns almost immediately with an identifier, the work of checking the target happens in the background, and the next thing to ask arrives later on a message stream the page is listening to. That is also what makes the sequence safe: the server decides what the next step is and hands the client something it has to give back, so a step cannot be skipped, repeated or reordered from the outside.

The handoff between the wizard and the engine that runs the scan is deliberately almost empty. It carries an identifier and an address to report back to. Everything else was written to shared state a moment earlier, and the engine reads its own configuration from there. Passing a payload instead would mean two services agreeing forever on the shape of that payload, and they would stop agreeing the first time either one changed.

Three ways in, and only one pipeline

Three quite different things start a scan. A person clicking through the wizard in a browser. An external agent driving the same steps headlessly, with no browser and no message stream, getting back an envelope on every reply that says what it should ask for next. And a scheduler that wakes up once a minute, finds the scans that are due, and starts them.

None of those is a separate engine. They are the same handler behind three different front doors, and the difference between them is how the caller proves who it is and how it is told what to do next. A recurring scan is not a second scanner. It is a timer that calls the wizard a human would have called.

That sounds obvious written down, and it is the kind of thing that stops being true the moment somebody is in a hurry. A scheduled scan that drifts from an interactive one is a bug you find months later, in a report, in front of a customer.

One smart login, then many fast replays

The scans worth running are against applications behind a login, and a scripted login breaks the moment somebody moves a button or turns on a second factor. So an agent does it: here are the credentials, here is what success looks like, work it out. It reads the page and decides whether it landed on a dashboard, hit a second factor, or got blocked. It can generate a one-time code and type it into whichever field it judges to be the right one.

The trick is doing that exactly once. Running an agent for every request would be unaffordable, so the moment it gets through, the session it earned is captured and replayed for everything after. The judgment happens once. The thousands of requests that follow are scripted and fast.

This is the shape the rest of the system copies. Find the one decision that genuinely needs judgment, pay for it properly, and turn its answer into something cheap that everything downstream can use without asking again.

the judgment happens onceloginan agent reads the pageand decides where it landeda session, earnedeverything after replays it128 requests, scripted, no model involvedone call, and the only one worth paying for
The login takes a third of the loop on its own. Everything behind it fits in less.

Every scan gets its own tools

The scanners are not a shared fleet sitting there waiting. When a scan starts, the tools it needs are brought up for that scan alone, on one host, talked to over the loopback, and thrown away when it finishes.

That is more machinery than calling a service that is already running, and it buys two things worth the trouble. One scan cannot be affected by another, which matters when the thing you are running is deliberately hostile traffic. And for a target that is only reachable through a private network, the tunnel and the tools end up on the same host, which is the only arrangement that makes that case simple rather than a special path through everything.

The model judges, the code scores

A raw scanner result is not something you put in front of an auditor. It needs a title, remediation steps and a severity score. A model is good at the first and untrustworthy at the last: ask for a score directly and you get a confident number that does not survive being checked.

So it never produces the number. It produces the reasoning behind it, and a calculator turns that into the score. A severity vector is written by the model and computed by a standard implementation. A five part risk breakdown is judged by the model one part at a time and averaged by code. The model supplies structured inputs. The code supplies the arithmetic, and the arithmetic is the part anybody will argue with.

The rule underneath is that nothing ships unless it points at evidence a tool actually found and that still exists when the report is assembled. It reports less than it could. A report somebody has to double-check is not a report.

Two places out of seven

Everything else resolves against a table. Which tools run, whether a test proceeds, what a scan costs, what happens when a schedule fires: none of it asks a model anything.

Even the metering follows the same instinct. A scan is held against an account's balance before it starts and settled when it finishes, and every failure in that path that is not a genuine shortfall lets the scan run. A billing system that cannot reach its own store should not be the reason a customer's security testing does not happen.

1target2tool plan3login4scan5findings6scoring7reporttwo places out of sevena model decidesa table decides
The product reads as end to end AI, and it should. Every deterministic step exists so the two where a model decides can be trusted.

Looking back

I went in expecting the work to be about getting a model to do more. It was about finding the fewest places one was necessary, then building enough around them that their answers could be relied on. None of that is visible when it works, which is the job.