October 28, 2026
The AI Wrote Your Code. The AI Reviewed Your Code. The AI Approved the Pull Request. LOL.
Three AI agents. Zero disagreements. One beautifully automated security vulnerability.
By Yua Mikanana
3 min read
Monday, 9:14 AM.
A developer asks an AI coding agent to build an authentication endpoint.
The agent writes 400 lines of Python, creates unit tests, updates the documentation, and opens a pull request.
Time taken: four minutes.
The developer is delighted.
Productivity is up 900%, according to a metric somebody invented for the quarterly board presentation.
9:19 AM.
An AI code-review agent examines the pull request.
It checks code quality, naming conventions, formatting, and potential security issues.
Result:
APPROVED โ No significant issues detected.
9:21 AM.
Another AI agent runs the security review.
PASS โ Authentication implementation follows best practices.
9:23 AM.
CI/CD merges the pull request.
Three AI agents have independently agreed that everything is fine.
Except they haven't.
They've all missed the same vulnerability.
And nobody has actually read the code.
Here's the problem with automated consensus.
Imagine the coding agent generated this authorization check:
@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int, user=Depends(get_user)):
invoice = db.get_invoice(invoice_id)
if not invoice:
raise HTTPException(status_code=404)
return invoice@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int, user=Depends(get_user)):
invoice = db.get_invoice(invoice_id)
if not invoice:
raise HTTPException(status_code=404)
return invoiceLooks clean.
Authentication exists.
The user must be logged in.
The endpoint handles missing invoices.
Beautifully formatted.
There's just one tiny issue.
It never checks whether the authenticated user owns the invoice.
An authenticated user who knows another invoice ID might retrieve somebody else's financial data.
Classic broken object-level authorization.
OWASP has been warning about this category of API vulnerability for years.
But don't worry.
The AI reviewer said the code follows best practices.
Perhaps the best practice is giving every customer access to everybody else's invoices.
Very collaborative.
Tuesday, 11:42 AM.
A human security engineer finally reviews the endpoint.
They ask one question:
"Where's the authorization check?"
The development team looks confused.
"The AI already reviewed it."
"Which AI?"
"The security AI."
"Who reviewed the security AI's findings?"
"Another AI."
"Who reviewed that?"
Silence.
Somebody opens Jira.
This is the part that interests me more than the usual argument about whether AI writes good code.
What happens when the same technology generates the work, evaluates the work, and decides the work is acceptable?
It's a correlated-failure problem.
Imagine three human reviewers who learned from the same textbook, use the same checklist, and share the same mistaken assumption.
They might all miss the same bug.
Now imagine three LLM agents using similar models, prompts, training patterns, and security heuristics.
Calling them independent reviewers doesn't magically make their judgments independent.
Three confident answers can still represent one underlying mistake.
And a green checkmark doesn't become more trustworthy just because another green checkmark agrees with it.
Wednesday, 2:06 PM.
The engineering manager announces that AI has reduced pull-request review time by 85%.
Wonderful.
But what exactly improved?
Time to approval?
Number of comments?
Lines of code generated?
Those are productivity metrics.
They aren't security outcomes.
An AI reviewer can catch obvious secrets, dangerous functions, missing validation, and familiar insecure patterns.
That's useful.
But business-logic vulnerabilities are harder.
The reviewer needs to understand who is allowed to access which resource, under what conditions, and why.
A model can recognize a missing authorization check.
It can also fail to recognize one when the relevant business rules aren't in its context.
The real question isn't whether the code looks secure.
It's whether the implementation satisfies the actual security requirements.
Those are very different things.
Thursday, 10:31 AM.
The security engineer submits a fix.
@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int, user=Depends(get_user)):
invoice = db.get_invoice(invoice_id)
if not invoice:
raise HTTPException(status_code=404)
if invoice.tenant_id != user.tenant_id:
raise HTTPException(status_code=403)
return invoice@app.get("/api/invoices/{invoice_id}")
def get_invoice(invoice_id: int, user=Depends(get_user)):
invoice = db.get_invoice(invoice_id)
if not invoice:
raise HTTPException(status_code=404)
if invoice.tenant_id != user.tenant_id:
raise HTTPException(status_code=403)
return invoiceBetter.
Now the endpoint checks tenant ownership.
But even this assumes the tenant boundary is the correct business rule. Perhaps users should only see invoices assigned to their own accounts.
That's the point.
Security isn't merely recognizing dangerous syntax. It's understanding intended behavior.
A useful automated test would explicitly verify that a user from another tenant cannot retrieve the invoice:
def test_cross_tenant_invoice_access(
client, tenant_a_token, tenant_b_invoice
):
response = client.get(
f"/api/invoices/{tenant_b_invoice.id}",
headers={
"Authorization": f"Bearer {tenant_a_token}"
}
)
assert response.status_code in (403, 404)def test_cross_tenant_invoice_access(
client, tenant_a_token, tenant_b_invoice
):
response = client.get(
f"/api/invoices/{tenant_b_invoice.id}",
headers={
"Authorization": f"Bearer {tenant_a_token}"
}
)
assert response.status_code in (403, 404)Now there's a concrete security property to enforce.
Not just an AI saying everything looks lovely.
And if the coding agent writes the test, the reviewing agent should still check whether it covers the actual threat model.
Otherwise, you've merely automated the production of reassuring evidence.
Friday, 4:58 PM.
The company announces its new AI-first software development lifecycle.
Coding: AI.
Testing: AI.
Code review: AI.
Security review: AI.
Documentation: AI.
Deployment: AI.
The CEO posts on LinkedIn:
"We've achieved unprecedented engineering velocity through autonomous development."
Forty-seven fire emojis.
Six people comment, "This is the future."
Nobody asks whether the AI-generated tests would have caught the AI-generated authorization bug.
The security engineer does.
Their comment gets three impressions.
I'm not arguing that humans should manually review every line of AI-generated code forever.
That would defeat much of the point of automation.
Use AI to generate code.
Use AI to review code.
Use AI to propose tests.
But give those systems explicit security requirements, enforceable policies, independent checks, and meaningful human review where the risk warrants it.
Measure escaped vulnerabilities, not merely merged pull requests.
And don't confuse agreement between AI agents with independent verification.
Because when your coding agent, reviewing agent, and security agent all share the same blind spot, you haven't created defense in depth.
You've created three layers of artificial reassurance.
Friday, 5:01 PM.
Production incident declared.
5:02 PM.
The AI incident-response agent begins investigating.
5:03 PM.
It identifies the vulnerable code.
5:04 PM.
It recommends asking the original coding agent to fix it.
5:05 PM.
The coding agent submits a pull request.
5:06 PM.
The AI reviewer approves it.
5:07 PM.
The AI security agent confirms everything is secure.
5:08 PM.
The human security engineer turns off their laptop.
They're going to become a goat farmer.