Skip to main content

AI-written code

Assistants changed how much code you write. They did not change how software fails.

The defects arriving in your product are the same ones that have always arrived. There are simply more of them, they arrive faster, and the person merging them read less of the change than they used to. Vulkro is the review that keeps up: it checks each file as the assistant writes it, gives the same verdict on the same code every time, and runs entirely on your machine, so neither your code nor whatever you told the assistant about your business goes anywhere.

  • Checks each file as it is written
  • Same verdict every run
  • Runs on your machine
  • Nothing uploaded

01 / What changed

The bottleneck moved from writing to reviewing

Writing code stopped being the expensive part of shipping. Checking it did not get any cheaper, so that is where the queue formed.

A team that used to open a change of forty lines now opens one of four hundred. The change is coherent, it is well structured, and it was produced faster than anyone can read it properly. So the reading gets thinner. Attention per line falls, the reviewer checks that the feature works and that the shape looks familiar, and the approval arrives in a fraction of the time the change took to produce.

Nothing about that is an argument against the assistant. It is an argument about arithmetic. The number of chances to introduce a defect went up sharply and the number of hours available to catch one did not move at all. A security review that depends on a person reading carefully is a review that now runs at a fraction of the coverage it had two years ago, even though nobody decided to lower the standard.

The failure modes themselves are unchanged. Nothing on the list below is new. What is new is how often they arrive and how little attention each one gets on the way in.

02 / The shapes that repeat

Five defects that turn up again and again

These are not exotic. They are the ordinary failures of web software, and a model produces them for the same reason a hurried engineer does: the code that is missing does not look missing.

  • A new endpoint without the check its neighbours have

    The assistant copies the shape of a route but not the access check around it. The endpoint works, the tests pass, and anyone who knows the address can call it. This is the single most common defect in generated web code, and it is invisible in review because the missing thing is missing.

  • A record fetched by id, with nobody checking who asked

    The handler reads an identifier out of the address and returns whatever matches. Change the number in the address bar and you are reading another customer’s data. The code is correct in every respect except the one that matters.

  • A request body written straight onto a stored record

    Convenient, idiomatic, and it lets a caller set fields you never meant to expose, including the ones that decide what they are allowed to do next.

  • An untrusted value that reaches a database query through other files

    The handler was written in one turn, the helper in another, and the query builder in a third. Each file reads as safe on its own. The defect only exists in the path between them, which is exactly the thing a reviewer reading a diff cannot see.

  • A credential written into the code as an example

    Models produce example keys that look exactly like real ones, and real ones get pasted in beside them. Both end up in the history, where removing the line does not remove the secret.

console - findings4 findings
SeverityFindingCWERule
CRITSQL injection in order lookupcheckout.py:214CWE-89VULK-1042
Dataflow pathVULK-1042
  1. 01checkout.py:214request handlersource

    order_id read from request.args, no type or format check

  2. 02services/orders.py:88helper

    passed through lookup_order(order_id) unchanged

  3. 03db/query_builder.py:41query builder

    concatenated into the WHERE clause with an f-string

  4. 04db/session.py:57sinksink

    cursor.execute(sql) runs the assembled statement

4 hops resolved. No sanitiser between the source and the sink.

HIGHMissing authorization on invoice downloadroutes/invoice.ts:47CWE-639VULK-2117
HIGHSSRF via user-supplied URLproxy-handler.ts:23CWE-918VULK-1180
MEDHardcoded API token committed to the repositoryconfig/stripe.js:9CWE-798VULK-3304
The review as your team reads it. Every row names the file and the line, and the open row shows the whole path a value travelled from the request that carried it to the query that ran it, across four files. That path is the part a reviewer reading a single diff cannot reconstruct.

Values are followed across file boundaries in Python, JavaScript, TypeScript and Go. In Java the same trace stops at the first file boundary, so a Java result on the same product is narrower than a Python or Go one. That is a gap in what we can do today rather than a clean bill of health, and it is set out in full on the language support page.

03 / Review in the loop

The check runs before you see the diff

Catching a defect at review time means somebody wrote it, somebody read past it, and somebody now has to go back. Catching it as the file is written means the assistant fixes its own work in the same turn.

Vulkro attaches to the assistant you already use. Every time the assistant writes a file, that file is checked, and a serious finding is handed straight back to it in language it can act on: this handler has no access check, this value reaches a query unescaped. The assistant rewrites the file and carries on. You see the corrected version, and often never learn there was anything to correct.

Two design decisions make that practical. Serious findings stop the write and minor ones do not, because an assistant trapped on a style nit is worse than no check at all. And the check reads one file with no network call and no model call, so it costs nothing per run and adds no delay a person would notice. A check that is slow or chatty gets switched off in the first week, and then nothing is checked.

# attach the check to the assistant you already use
$ vulkro guard install
  ✓ guard installed. every file written is checked.

# the assistant writes a new route. guard answers before it lands
  [HIGH]  Route added with no access check
         routes/exports.ts:31
         Every sibling route on this resource
         checks the session. This one does not.

# write blocked. the assistant rewrote the handler
  ✓ re-checked. no findings. file written.
The check attached to the assistant's write loop, then a file it refused to let through. The assistant received the finding, rewrote the handler, and the second write passed.

04 / A fix that is checked

A proposed change is only written when the finding is gone

Anything can propose a patch. The question worth asking is what happens when the patch does not work.

Vulkro can draft the change that closes a finding, and it can do that with a small model running on your own machine. What makes the draft worth anything is the step after it. The patched file is checked again from scratch. The change is written only if the finding has actually gone and the file still holds together. If it has not, the patch is thrown away and the finding stays open, which is a far better outcome than a file that looks fixed and is not.

That ordering is deliberate and it never reverses. The model is allowed to suggest. It is never allowed to decide whether a finding exists, and it is never allowed to decide whether one has been resolved. Those are answered by the same deterministic review that found it, which is why two people running the same check on the same code get the same answer.

vulkro fix --ai · VULK-10441 file changed
HIGHServer-side request forgery in the URL proxy
src/routes/proxy-handler.tsCWE-918VULK-1044+7 -2
Line beforeLine afterChangeSource
@@ -3,3 +3,5 @@
33import {HttpError} from '../http/errors';
44 
5Added line. const ALLOWED_HOSTS = new Set(['assets.example.com', 'files.example.com']);
6Added line.  
57const PROXY_TIMEOUT_MS = 5_000;
@@ -18,6 +20,9 @@ export async function proxyHandler
1820export async function proxyHandler(req: Request, res: Response) {
19Removed line. const target = req.query.url as string;
20Removed line. const upstream = await fetch(target);
21Added line. const target = new URL(String(req.query.url));
22Added line. if (!ALLOWED_HOSTS.has(target.hostname)) {
23Added line. throw new HttpError(400, `blocked upstream host: ${target.hostname}`);
24Added line. }
25Added line. const upstream = await fetch(target.href);
2126 
2227 res.status(upstream.status).send(await upstream.text());
2328}
A drafted change and its receipt. The file was re-checked after the patch was applied: the finding is gone and the file still parses, so the change was written. Had either test failed, the file would have been left exactly as it was.

05 / Four places it runs

The same review, at four points in the day

One engine, four positions. Each one catches what the position before it could not, and the earliest is by far the cheapest.

  1. 01

    While the assistant writes

    Each file the assistant writes is checked the moment it is written, and a serious finding goes straight back to the assistant, which rewrites the file before you have read a line of it. Serious findings stop the write; minor ones are reported without interrupting the work. The check reads one file, needs no network and costs nothing per run, which is the only reason it can sit in a loop this tight.

  2. 02

    Before the commit

    The same check, scoped to what you are about to commit, and comparing against where you started. Work that was already in the repository never blocks a commit. Only what this change introduced does.

  3. 03

    In the pull request

    The review comment arrives with the finding, the file, the line and a proposed change, so the conversation starts from something concrete rather than from a dashboard link somebody has to be given access to.

  4. 04

    Before the release

    The full review of the whole product, run as a gate. It either passes or it names what is blocking it, and because the verdict cannot move unless the code moves, a red build is a reason to look rather than a reason to re-run.

On a codebase with years of history behind it, the gate you want is the one that only ever fails on what a change introduced. Existing work is recorded once and burned down on your own schedule, so nobody is handed an unfinishable assignment on the day the gate goes in. The pipeline page covers that setup.

06 / Measured and bounded

What it finds, and what a clean pass does not prove

The result below comes from a test built out of real applications with real, publicly documented vulnerabilities in them, at the strict setting we recommend for a build gate.

Measured againstvulkro 0.18.0, measured 2026-07-18

Real vulnerabilities found and missed, and false alarms raised, on a test built from real applications.
What was measuredResult
Real vulnerabilities found47 of 83
Real vulnerabilities missed36
False alarms raised13
Every vulnerability in this test is a real, publicly documented bug in a real application, confirmed by reading the code at that exact version. The ones Vulkro cannot find stay in the test rather than being removed.

The missed column is published on purpose. A vulnerability this review cannot find stays in the test rather than being quietly removed, which is the only reason the found column is worth reading.

A clean pass is a screen, not a certificate
The review finds failure shapes it knows how to recognise. A defect nobody has taught it to see reads as clean, and a clean result should be read as "none of the known shapes are here" rather than as "this is safe".
Nothing is executed
This is a read of your code, not a test of your running system. A defect that only appears under load, or only with production data, is out of scope.
It does not judge whether the code is good
Design, naming, structure and whether the assistant chose a sensible approach are not assessed. This layer answers one question: does this code have a known security failure shape.
Ranking is not proof of exploitability
Findings are ordered by whether your own code reaches them, which is a defensible order to work down. It is not a claim that an attacker can reach any of them as you have things deployed.

There is a category of tool that reviews a change conversationally in the cloud, comments on readability and design, and is genuinely useful at that. It is a different layer from this one and the two sit together comfortably. Vulkro is the deterministic layer underneath: the same code gets the same verdict every time, nothing is sent to a model to decide whether a finding exists, and the context you gave your assistant about unreleased products and customer data stays on the machine it was typed on. That last point is a property of where the software runs, not a promise in a contract.

Where to go next

The pages around this one

The code an assistant writes is one surface. These are the neighbouring ones.

The review, and the team it belongs to

Vulkro reviews your codebase and Vulkro for Salesforce reviews your Salesforce build. Vulkro Red is the other half of the team: it takes what the review found and works out what an attacker would actually do with it.

The reviewVulkro

Reviews your code

Goes through every line of your codebase before a release, the way a senior engineer would if they had the time, and tells you what a customer could exploit.

You get: what to fix, and a pass or fail on the releaseWhat it checks
 Vulkro for Salesforce

Reviews your Salesforce build

The same review for the part of your business that runs on Salesforce, including the settings in the org itself, and what the AppExchange security review will ask you for.

You get: a straight answer on whether you are ready to submitVulkro for Salesforce
The red teamVulkro RedComing soon

Attacks what they found

Takes the review and works out what an attacker would actually do with it: which small problems chain together into a real break-in, and which ones are noise.

You get: the attack, step by step, before someone else runs itHow it works

One engine behind all three, so the red team works from what the review already foundIt all runs on your machine. Your code never leaves it.