Inspiration

In May 2017 WannaCry reached the NHS through software that was already known to be broken. The UK National Audit Office found that at least 80 of 236 English trusts were disrupted and about 19,494 appointments and operations were cancelled. Every infected organisation was running unpatched or unsupported Windows, and NHS Digital had sent critical patch alerts in March and April 2017. The fix existed. It was not applied. (NAO report on the WannaCry cyber attack.)

That is the gap I wanted to work on. A published security advisory says a flaw exists and a fix exists. It does not show, on your own machine, that the flaw is reachable or that the fix actually closes it. For a small clinic running open-source health software, confirming that is an evening of work nobody has time for.

When health software fails, the legal bill arrives years later. In re: Change Healthcare, Inc. Customer Data Security Breach Litigation is a multidistrict case in the District of Minnesota (MDL No. 3108, docket 0:24-md-03108, opened March 2024). In re Anthem, Inc. Data Breach Litigation is MDL 2617 in the Northern District of California (5:15-md-02617), filed in 2015. I checked both dockets on CourtListener. I have not verified how either case resolved, so I'm not claiming an outcome, only that the litigation is real and on the record. The enforcement side is clearer: HHS OCR fined Anthem $16,000,000 over a breach affecting roughly 79 million people, and fined Premera Blue Cross $6,850,000, citing its failure to run an enterprise-wide risk analysis. Premera had not looked for its own flaws before they were used against it.

I picked OpenEMR as the target because small practices actually run it, it's GPL-3.0, and it has 153 published security advisories, 13 of them rated critical. Its maintainers are volunteers who get an advisory report and whatever evening they can spare.

What it does

Given a public advisory that already has a published fix, the agent proves two things with an executed test, not a plausible-looking diff.

I ran the full loop on CVE-2026-24908, a SQL injection in OpenEMR's patient REST API sort parameter: vulnerable through v7.0.4, fixed in v8.0.0.

  1. It brings up the vulnerable version in a local Docker container and runs a regression test that measures query timing through the sort parameter. On the vulnerable build the test fails, exit 1 — the query takes 15 seconds longer than it should, which is the timing signature the test is built to catch. That's the flaw demonstrated, not asserted.
  2. It applies the maintainer's real patch and reruns the identical test against the fixed build. The test passes, exit 0.
  3. Nemotron 3 Ultra then reads the test output and the diff and gives a verdict on whether the patch closes the underlying mechanism or just happens to pass this one test.

Every step is logged: the raw PHPUnit output, the container state, the diff. Everything runs against containers I started on my own machine. The only artifact the agent produces is a test file, never a standalone payload meant to run against someone else's server.

How I built it

The backend is FastAPI; the frontend is React. Model calls go through Nebius Token Factory, which is OpenAI-compatible, so the whole thing runs on one base URL and the standard client.

Nemotron does two different jobs at two different tiers:

  • Nemotron 3.5-Lightning drives the actual tool loop: git checkout of the pinned vulnerable/patched tags, docker compose up, running the PHPUnit/HTTP test, reading back stdout and exit codes, applying the patch file, and reporting back. It's the Nemotron model tagged for function calling, and the cheapest of the four, which matters because this loop runs many small tool calls per advisory.
  • Nemotron 3 Ultra is reserved for one call per advisory: after the test goes green, it reads the test output plus the actual code diff and decides whether the patch addresses the real injection point or merely blocks the one payload the test happens to send. That's a judgment call, not a lookup, so it gets the strongest model instead of the cheapest one.

The real build gotcha was environment setup, not the model. OpenEMR's Docker images need MariaDB wired up correctly and the vulnerable/patched tag pair has to be pinned precisely (openemr:7.0.4 against the patched line through 8.0.0) or the diff between versions carries unrelated changes that confuse the "does the fix match the advisory" check. Pinning to a known-good tag pair removed most of that risk.

What makes it agentic

Perception Decision Action
Advisory text names a parameter and an endpoint locate the matching code path before writing anything read the file, confirm the sort parameter reaches the query unsanitized
Test run against the vulnerable tag times out short of 15s or returns clean the repro doesn't match the advisory yet rewrite the test's injection point and rerun before touching the patch
Test passes after the patch is applied the fix might only be cosmetic escalate to Nemotron 3 Ultra with the diff and the test output for a verdict, rather than reporting the green run as the end of the story

Accomplishments

  • A real, local Docker reproduction of CVE-2026-24908: fail on the vulnerable build (exit 1, the query measurably delayed), pass on the patched build (exit 0), with the raw test output kept.
  • A working credit-aware split between a cheap tool-calling model and a reserved model for the one hard judgment, which kept the build inside free credits.
  • Logs that show the actual command sequence and PHPUnit output rather than a narrated summary.

What's next

  • Run the same loop across the other reproducible OpenEMR advisories I catalogued (the blind SQL injection in the search popup and the REST insurance/employer search injection), each against its own pinned vulnerable/patched tag pair.
  • Pin the exact fix commit per advisory instead of relying on the tag-to-tag diff, since GitHub's advisory API didn't return commit links for these GHSAs.
  • Try the agent's sandboxing inside NVIDIA OpenShell for the container build step, which I didn't get to validate on real hardware before this submission.

Built With

Share this project:

Updates

Submission history