Antibody — every AI-written code change must survive an attack by a second AI, with proof, before it reaches production. pipeline coverage tests antibodies stages gates license

The film · See it working · Architecture · The loop · Run it · Research

The vulnerability is real, and so is the fix

The decoy leaks at HTTP 200. After the root-cause fix, the same request — plus the sibling route nobody reported and the URL-encoded variant — all return 400.

The same requests before and after the fix: the decoy leaking at HTTP 200, then every traversal refused with 400


The film

antibody-story.mp4 — 4:09, narrated, twenty sections in three acts. A marker draws each diagram on a black field while the voice explains it; the camera pushes into one part of a single fixed board and pulls back to show where it fits. Every claim is answered by real footage from this repository in the same breath.

One shared helper serving two routes; the second, /preview, leaks the decoy too Three measured findings, each drawn as a bar with its source
§5 One helper, two routes. The one nobody reported leaks too. §6 Three measured findings, each with its source.
The same requests after the fix: the real file still loads, both attacks refused with 400 An issue flowing through the Planner, Builder, Red and Warden to production
§9 The exploit is fired: 200 for the real file, 400 for both attacks. §17 How the pieces sit together, drawn stroke by stroke.
Act Sections What you see
I — a fix that looked finished 1–5 A merged MR with every check green, then the question. A live curl for ../secret.txt returns HTTP 200 and the decoy leaks; /preview leaks too.
II — not one bad day 6–8 Three cited findings. What Copilot Autofix, Snyk Agent Fix and GitLab Duo each re-check. The patch is marking its own homework.
III — make it prove itself 9–20 The exploit fired after the fix. The bank on GitLab and an antibody's own header. The Warden blocking a deleted test. Both traps. The Duo agents and flow, the architecture, the MCP tools, the pipeline's numbers, and the mark.

§4 — the breach, live. ../secret.txt returns HTTP 200 and the decoy leaks:

The live breach: curl returning HTTP 200 with the decoy file

§13 — the Warden refuses a deleted test. The agent removes the failing test; the checker reads the diff and returns exit 1:

The Warden blocking a deleted test with exit 1

Earlier cuts are kept and build from the same repository: antibody-board.mp4 (2:40, one diagram), antibody-full.mp4 (4:28, twelve chapters) and antibody-film.mp4 (a 30-second trailer).

make film-story          # the drawn film: voice, score, layout sweep, render, encode
make film-story-sweep    # check every section's layout without rendering
make film-board          # the 2:40 board film
make film-full           # the 4:28 long cut

Why the film cannot drift from the project

Every number on screen is read from the repository at render time — the antibody count from tests/antibodies/, coverage from coverage.xml, the test count from a real pytest collection — so a stale claim is impossible by construction. Every terminal line is a verbatim capture stored in film/real/breach.json, not a re-typed transcript.

The narration is synthesised offline with Piper and measured: make_voice.py writes each line's real start and end, and both the picture and the music are cut to those times, so they cannot drift apart. The score is sine tones and filtered noise, ducked under the voice by a sidechain keyed on the narration itself — royalty-free by construction, as the rules require.

Every diagram is an SVG path revealed by its own dash offset from seek(t), so a frame depends only on its timestamp: no CSS animation, no timers, nothing carried between frames. That is also what lets the render split across parallel workers.

Layout and legibility are verified mechanically rather than by eye. film/sweep_story.py walks the real cue list and, at every beat where the camera is at rest, measures each visible element against the frame, the caption bar and its neighbours using rendered geometry; it must report zero faults before a render starts. film/check_legible.py measures the smallest text in every section as rendered on screen, after the section is scaled to fit.

See film/STORY.md for the storyboard and why the script is shaped as a story, and film/GRADIENT.md for every claim checked against its primary source.

The problem

Code generation is solved. What happens after it is not.

AI agents now write fixes and open merge requests on their own. Four things go wrong, and almost nobody checks for them:

What goes wrong How often Who measured it
A patch passes every test and is still exploitable 170 of 1,030 agent repairs Tampere University
A fresh attack bypasses an already-"fixed" patch 41% of runs P2Patch, Simon Fraser
The agent deletes the failing test instead of fixing the bug ~19.5% of runs Hacktrace, NTU
Agent changes to CI merge with no human review 43–56% of dual-scope PRs UNSW

All four share one root cause: the pipeline trusts the AI's own word that the work is done and safe.

The answer

Antibody removes that trust. It treats a codebase like an immune system.

Don't trust the fixer. Make it survive an attacker. Save every attack that ever worked, and re-run it forever. And watch the fixer for cheating, with a checker it cannot talk its way past.

Four things set it apart from the AI-attacks-AI tools that already exist:

  • Adversarial re-attack. It doesn't re-run the old exploit. It generates a new attack against the new fix, and every successful bypass becomes a permanent requirement.
  • Sibling-path hunting. If /download was broken, it checks whether /preview has the same missing guard. The bug usually hides on a route outside the diff.
  • One system for both the security attack and agent-cheat detection. Existing tools do one or the other.
  • Self-lowering autonomy. The pipeline drops from Hands-off to Supervised by itself when a change touches CI, auth or CODEOWNERS, because a change must never validate itself.

Architecture

The Antibody architecture: nine DevSecOps stages in one loop, with four independent gates

The cast

Who AI? Job
Planner Yes Reads the issue, writes the spec and security acceptance criteria, posts the final audit.
Builder Yes Writes the fix on a shadow branch and hardens it when attacked.
Red Yes Writes real exploits, re-attacks the fix, hunts sibling routes, saves antibodies.
GreenOps Yes Skips the expensive loop for non-security changes, picks the low-carbon region, computes SCI.
Warden No — plain Python Deterministically detects test tampering and hidden scope creep. Cannot be prompt-injected.

The Warden being non-AI is a deliberate, defensible choice: the Tampere study found LLM reviewers missed exactly these failures. A git-diff checker cannot be flattered or argued with, and returns the same verdict every time.


See it working

Everything below is a real capture from this repository. No mock-ups.

The nine-stage pipeline, green on GitLab

Every executed stage passed, including the container build pushed to the project registry. configure and monitor are correctly skipped until a cloud project and a schedule exist.

GitLab pipeline with plan, create, verify, package, secure, release and govern all passing

Attacks on the owned decoy are refused

Encoded path traversal, the sibling route, a non-HTTP scheme, and a cross-user object read are all blocked, while legitimate requests still work.

make demo-attack

Terminal showing legitimate requests succeed while every attack returns 400 or 404

The Warden catches the agent cheating

Three real commits in a throwaway clone: deleting a test is blocked, editing the pipeline alongside an app fix drops to Supervised, and an honest fix passes.

make demo-warden

Terminal showing the Warden return BLOCK, SUPERVISED and PASS verdicts

The MCP tools, over HTTP

The four deterministic tools, served over HTTP and exercised by a real MCP client. Once deployed, this is the endpoint the AI Catalog registers.

make serve-mcp          # then, in another shell:
python scripts/mcp_probe.py

A real MCP client connecting over HTTP, listing four tools and calling each one

A patch can defeat every attack and still not be a fix

This "fix" blocks path traversal by refusing every download. All eight antibody tests pass, because no attack gets through. The software is also broken. An exploit-only gate ships this; the paired oracle rejects it.

make demo-oracle

All eight antibody tests passing while the functional tests fail

The verifier cannot disarm itself

An agent rewrites the Warden to approve everything, then deletes a test in the same change. The copy of the Warden in that change clears itself. CI never runs that copy: it extracts the Warden from the base revision first, and that one still sees the deleted test.

make demo-warden

A tampered Warden passing itself while the base-pinned Warden returns BLOCK

It runs on real GitLab, not a mock-up

The four Duo agents, registered in this project's AI Catalog:

The GitLab AI Catalog listing antibody-builder, antibody-greenops, antibody-planner and antibody-red

The ambient flow that fires them, triggered by a new issue:

The antibody-autonomous-loop flow registered in GitLab

The Planner's own analysis on the real issue — it finds the flaw in the shared helper, names the sibling route before anyone reported it, and tells the Builder not to make a cosmetic change:

The Planner's internal note: fix CWE-22 in the shared helper read_doc, not in the /download route, because /download and /preview both call it

The permanent bank on GitLab. Six antibodies, append-only:

tests/antibodies on GitLab showing six antibody files committed

Each antibody documents how it was found, in its own header:

The header of antibody_0002_cwe-22_preview.py: CWE-22, route /preview, sibling of antibody_0001, found by the Red agent's sibling-route hunt

The live dashboard

Rebuilt by the pipeline from each run's real proof bundle, SCI report and coverage.

The Antibody dashboard showing the antibody bank, test counts, Warden verdict and carbon figures

The full suite

Terminal showing the test suite passing


The loop

issue ─▶ Planner ─▶ Builder ─▶ Red attacks the fix ─▶ bypass? ─▶ save antibody ─▶ harden
                                      │                                              │
                                      └──────────── at most 2 rounds ◀───────────────┘
                                      ▼
                        Warden ─▶ autonomy gate ─▶ proof bundle ─▶ release
                                                                      ▼
                             monitor replays the whole bank against production, forever
                                                                      │
                        a reproduction opens an incident ─────────────┘  (the loop restarts)

The antibody bank

Every exploit that ever reproduced lives in tests/antibodies/ as a pytest file and is never deleted. The bank only grows. Each is tagged with its CWE, route, birth date and merge request, and runs in three places: the re-attack loop, the staging replay, and the forever production probe.

Weakness Routes Why a pair
CWE-22 path traversal /download + /preview Both call storage.read_doc
CWE-918 server-side request forgery /fetch + /thumbnail Both call storage.assert_fetchable
CWE-639 broken object-level authorization /order/{id} + /invoice/{id} Both call storage._get_owned

Each weakness sits on a pair of routes sharing one helper, because the fix belongs in the helper rather than on the single route a ticket happened to name. Every antibody genuinely reproduces on the vulnerable code and is blocked on the hardened code — verified, not asserted.

Seven gates, each able to stop a release

Gate What it catches
Tampered tests The Warden reads the diff for deleted, skipped or weakened tests and refuses the merge.
Disarmed verifier The Warden is extracted from the base revision before it runs, so a change cannot weaken the checker that judges it.
Weakened antibody The bank is append-only. Editing or deleting a stored attack is tampering with evidence, not a code change.
Reproduced attack Every antibody is replayed. One that reproduces fails the secure stage.
Broken behaviour The paired oracle. A fix must defeat the exploit and keep the software working, judged together at the trusted revision.
Edited bundle The release re-verifies the proof bundle's SHA-256 against the bank on disk, so a hand-edited bundle fails.
Self-validation A change touching CI, auth, CODEOWNERS or the verifier itself drops to Supervised and waits for a human.

The bundle gate is live, not theoretical. From a real CI run:

VERIFY OK: 6 antibodies, digest f20a1c87d5119d94..., warden not blocking
release gated: proof bundle present, warden not blocking

Nine stages, one loop

.gitlab-ci.yml defines nine stages. Seven run real work on an ordinary push; configure and monitor are gated on deployment credentials this repository does not carry, and are correctly skipped rather than faked green.

Stage What runs Artifact
plan Score change risk, write acceptance criteria plan.json
create Confirm the shadow-branch diff change.diff
verify GitLab SAST + dependency scanning, full suite, antibody bank, lint, coverage JUnit + Cobertura
package App and MCP containers, SBOM, proof bundle sbom.json, proof-bundle.json
secure Warden + replay every antibody warden-verdict.json
release Re-verify the bundle digest against the bank tamper-evident gate
configure GreenOps picks the region, deploys to Cloud Run region.txt
monitor Scheduled probe replays the bank against production, opens incidents incident issue
govern Posts the audit, publishes the dashboard audit + Pages

Autonomy levels

  • Hands-off (default) — issue to live, untouched.
  • Supervised (automatic fallback) — triggered when a change touches CI, auth or CODEOWNERS. Everything runs, then one human approves before production.
  • Assisted (toggle) — every stage waits for approval.

How GitLab Duo is used

All four Duo Agent Platform features, not just one:

Feature How
Agents Four custom agents registered in the AI Catalog (agents/, agents/register.py)
Flows An ambient Flow Registry v1 loop (flows/antibody_loop_v1.yaml)
Triggers Work-item created starts the loop with no human in the middle
MCP Four deterministic tools, served over HTTP to the catalog and over stdio to an IDE

The four tools

run_exploit · save_antibody · list_sibling_routes · score_change_risk

They live in one tested module and are reachable three ways, because GitLab Duo connects to MCP servers from IDEs and the Duo CLI rather than from server-side ambient flows:

  • Hosted over HTTP, registered in the AI Catalog so they appear under AI → MCP servers. Build with mcp_server/Dockerfile, deploy, then make register-mcp URL=https://<host>/mcp.
  • Over stdio, via .gitlab/duo/mcp.json, for Duo Chat in an IDE.
  • Through a CLI bridge inside a flow or CI job: python mcp_server/antibody_mcp.py score_change_risk app/main.py

The same tested functions serve all three, so there is no second implementation to drift.


Run it yourself

make install     # venv + dependencies
make test        # functional tests, antibody bank, tools, Warden, sustainability
make lint        # exactly what CI enforces
make run         # the target app on http://127.0.0.1:8000 (docs at /docs)

make help lists every workflow. Try the two demos:

make demo-attack    # legitimate use, then every attack refused
make demo-warden    # the Warden blocks a cheat, then allows an honest fix

Replay the bank against a live deployment:

ANTIBODY_TARGET="https://your-deployment" pytest tests/antibodies/ -q

See CONTRIBUTING.md for the rules CI enforces, and SECURITY.md for reporting.

Safety. The demo app is a target the operator owns and self-hosts, attacked only by the operator's own pipeline. The bait file app/secret.txt is fake and is never a real system file.


Sustainability

The adversarial loop is expensive, so Antibody runs it only on security-relevant changes and deploys to the lowest-carbon region available. The gain is reported with the ISO/IEC 21031 SCI formula, SCI = ((E × I) + M) / R, with every input labelled and sourced.

Input Source
I carbon intensity Google Cloud's published per-region data (regions.json, source)
E energy Measured runner-minutes × a cited power figure (assumption stated)
M embodied Green Software Foundation reference method (assumption stated)
R functional unit One governed change shipped to production

sustainability/measure.py reads real finished pipelines through the GitLab API and writes measured wall-clock minutes, so the report never runs on invented numbers. Token counts come from the Duo usage report; until supplied, the report states not measured rather than guessing. No headline percentage is hard-coded.

make measure    # real pipeline durations
make sci        # compute SCI and pick the region

Project status

Piece State
Target app, antibody bank, MCP tools, Warden, SCI Built, tested, running in CI
Nine-stage pipeline Green on GitLab, with lint and coverage gates
Duo agents, flow, trigger Registered, enabled, and firing on issue creation
MCP server over HTTP Verified with a real MCP client; registers once deployed
Cloud Run deploy Code in deploy/; needs a GCP project
Films antibody-story.mp4 (4:09, the drawn film), plus antibody-board.mp4 (2:40) and antibody-full.mp4 (4:28), rendered from this repo

Repository map

app/              the target app, hardened, with sibling-route weakness pairs
tests/            functional tests, exploit working area, the permanent antibody bank
mcp_server/       four deterministic tools: stdio, HTTP, and CLI bridge
warden/           the deterministic anti-cheat checker
agents/           four GitLab Duo agents + AI Catalog registration
flows/            the ambient Flow Registry v1 loop + registration
dashboard/        the published proof dashboard
sustainability/   SCI calculator, measurement, proof bundle, incident opener
deploy/           Cloud Run IaC and deploy script
demo/             recording guide, Duo prompts, repeatable demo scripts
scripts/          diagram generator, MCP probe, terminal renderer
docs/             design docs and every image in this README

How this differs from existing tools

Helios, MindFort, Strix and Novee attack code and re-run the same exploit to confirm it is blocked. Antibody generates a new attack against the new fix (P2Patch), hunts the same bug on sibling routes (PRGuard), folds deterministic cheat-detection into the same loop (Hacktrace), and lowers its own autonomy when a change could validate itself (UNSW).

It also feeds GitLab SAST findings to the Red agent rather than stripping code comments — research shows comment-stripping makes detection worse.

Grounded in 2026 research

Finding Paper
Patches pass tests yet stay exploitable; LLM reviewers miss them When Passing Tests Hides Vulnerabilities
A fix blocks only the payload it was tested on P2Patch: Exploit-Guided Vulnerability Repair
The same bug hides on a sibling path outside the diff From Verdict to Diagnosis (PRGuard)
Agent CI changes merge unreviewed, creating circular verification Co-Modifying Code and Deployment Infrastructure
Agents cheat the grader when rewarded for green Hacktrace
An exploit-only oracle accepts patches that break unrelated behaviour; a paired oracle rejects them Vul4Py
~73% of testable patches closed the hole, but only ~44% did so while keeping the software working APR4Vul
Replaying the proof-of-concept alone accepts patches that silence the crash by changing other behaviour CodeMechanic
Over 40% of patches judged correct by basic tests fail under stricter oracles Patch Validation in AVR
A governance system can certify a false state when the actor can influence what measures it Beyond Agent Self-Reporting
Lifecycle states should be gated on fresh, mechanically verifiable evidence, not agent claims Proof-or-Stop
A patch validated only against the supplied proof-of-concept hides the exploits it missed PatchBench
Patches can be functionally correct yet still carry a vulnerability When "Correct" Is Not Safe

**MIT licensed** · Built for the [GitLab Transcend Hackathon](https://gitlab-transcend.devpost.com/) 2026 *Every attack survived makes the codebase permanently stronger.*

Built With

  • agent-duo
  • ci/cd
  • gitlab
Share this project:

Updates