---
title: "Zero-Day to Zero Doubt: AI-Powered CVE Forensics in an Afternoon"
date: 2026-07-31
url: http://blog.quent.in/blog/2026/07/31/zero-day-to-zero-doubt-ai-powered-cve-forensics-in-an-afternoon/
---


On July 29, the Rails team [released security patches](https://rubyonrails.org/2026/7/29/Rails-Versions-7-2-3-2-8-0-5-1-and-8-1-3-1-have-been-released) for [CVE-2026-66066](https://discuss.rubyonrails.org/t/cve-2026-66066-possible-arbitrary-file-read-and-remote-code-execution-in-active-storage-variant-processing/91432) — an arbitrary file read (and potential RCE) through Active Storage, internally dubbed **KindaRails2Shell**. Found independently by researchers at [Ethiack](https://ethiack.com/) (0xacb, s3np41k1r1t0, castilho) and [GMO Flatt Security](https://flatt.tech/) (RyotaK), it's the kind of vulnerability that makes you pause.

If your Rails app uses libvips for image processing (the default since Rails 7), an attacker could upload a specially crafted file and read anything off your server. `config/master.key`. `/proc/self/environ`. Your database credentials. All returned as pixel values in a rendered image. And if they got `secret_key_base`, the file read is just the beginning — forged signed IDs and encrypted cookies turn it into RCE.

At [Rootly](https://rootly.com), where I'm CTO and CISO, we patched the same day. But here's the thing about patching: it tells you the door is closed. It doesn't tell you whether anyone walked through it while it was open.

Alongside the patch, DHH [posted](https://x.com/dhh/status/2083193882347487242) a link to [rails-forensics-CVE-2026-66066](https://github.com/rails/rails-forensics-CVE-2026-66066) — a forensic toolkit built from a real investigation at 37signals and shipped as **agent skills** for Claude Code. Not a script you run manually and stare at output. Not a wiki page with SQL queries to copy-paste. Agent skills — structured instructions that an AI agent follows to investigate your specific application, adapt to your stack, and produce a written determination.

That was new. And it changed how we approached the next question.

## What Rails shipped (and why it matters)

The toolkit answers two questions, in order:

1. **Was I vulnerable?** (`kr2s-was-i-vulnerable`) — Determines your exposure window: when the vulnerable code reached production, when the fix landed, and whether the preconditions for the attack were met.

2. **Was I exploited?** (`kr2s-was-i-exploited`) — Sweeps your Active Storage for the structural signature a crafted attack file has to carry, recovers what any confirmed file actually read, and produces a written forensic analysis.

The attack exploits a type confusion stacked twice across three layers that can't see each other's fields — Rails, libvips, and libmatio each reading different bytes to make different decisions about the same file. The [reference documents](https://github.com/rails/rails-forensics-CVE-2026-66066) trace the full call chain if you want the details. What matters for the investigation is what the attack *leaves behind*: a crafted file has to claim two incompatible things in its header — "MATLAB level 5" for libvips, "MAT 7.3" for the HDF5 reader. No legitimate file carries both. So the scanner reads the first 128 bytes of every candidate and checks those two fields. If both are present, that file was built for this exploit.

But here's what makes the toolkit more than a scanner: it's built as a **guided investigation**. The skill documents what each artifact proves, where the method runs out, what a clean result does and does not establish, and — crucially — what signals point toward a researcher's proof versus actual exfiltration. It includes a determination rubric with explicit anti-patterns: things that *look* like evidence but aren't. Like timing that clusters near a public disclosure (that's when opportunistic exploitation spikes too), or a small number of findings (a careful attacker reads few bytes on purpose).

This is the kind of reasoning that usually lives in a senior security engineer's head. Having it codified as agent instructions means the AI applies it systematically, every time, without drift.

## How the skills onboard the agent

What surprised me most was reading the `AGENTS.md` — the instructions the agent sees when it opens the repository. It reads like onboarding a careful junior engineer:

> *"This repository is not the application under investigation. Do not scan it, do not look for crafted files in it. It is the tool. The subject is a Rails application checked out somewhere else on disk."*

The skill explicitly tells the agent to keep two roots separate — the forensic toolkit and the target application. It forbids modifying the target. And for anything that touches production:

> *"Never run anything against their production database or object store yourself. You prepare the exact command, say what it does and how long it takes, hand it to the operator, and wait for them to return the output."*

The agent is positioned as an advisor, not an executor. It does the analysis, prepares the commands, and defers execution to a human who can verify. Even the scanner's `--since` and `--until` flags are required with no defaults — because "a default scans the wrong years for every application except the one it came from, and reports a clean result for a period nobody asked about."

These aren't technical constraints. They're judgment calls encoded as instructions. The kind of thing you'd spend twenty minutes explaining to a new team member before letting them near a production console.

## What the AI actually did

I pointed Claude Code at the skills and said go. Here's what happened over the next few hours — not a summary of what I did, but what the agent did while I occasionally checked in.

**Vulnerability assessment.** The agent traced through our codebase, found when we switched to libvips, confirmed variant tracking was enabled for the entire window, checked whether direct uploads were on, and established the exposure window from deploy dates (not commit dates — a distinction the skill explicitly calls out). It identified that our Active Storage tables use soft-delete, which meant the scanner needed adaptation.

**Scanner adaptation.** The upstream scanner assumes vanilla Active Storage. Our app had three differences the agent caught and patched: soft-deleted records needed `with_deleted` queries to surface purged evidence, PostgreSQL instead of MySQL for a timeout query, and no database replica role configured. It also identified two code paths where libvips processes files *outside* the normal variant pipeline — places where a crafted file could be processed without leaving a variant record. Neither was covered by the scanner, and both needed separate investigation.

**Parallel log analysis.** While I was sorting out how to get the scanner running in production, the agent ran Datadog log queries in parallel. It pulled upload volumes by day, checked for scripted user agents, looked for rejected attempts, and — most valuably — checked whether anyone had used the one code path that could destroy forensic evidence. That query alone turned "we can't rule out cleanup" into "we ruled it out for the window we can see."

**Production execution.** The agent built an ECS task command that downloaded the scanner from S3 and ran it against production, diagnosing and fixing infrastructure issues along the way until the sweep completed.

**Analysis.** Scanner came back: tens of thousands of candidates examined, zero crafted files found. The agent then drilled into the few hundred blobs it couldn't classify (S3 objects deleted, database records surviving thanks to soft-delete), analyzed their metadata, and investigated the two blind-spot code paths by querying the database for every file that passed through them during the exposure window. It produced a written forensic analysis with stated limits, following the toolkit's template.

**Total wall-clock time: about 4 hours.** Most of that was the scanner running and me at the gym. The investigation itself — the adaptation, the log analysis, the blind-spot checks, the written determination — would have taken days if I'd done it manually. Not because any single step is hard, but because the number of steps is large and each one requires context from the previous ones.

## Why agent skills are more than automation

When my scanner returned zero crafted files, the agent didn't just say "all clear." It enumerated what the result establishes (no blob with the crafted header pair exists in the database today), what it doesn't establish (that nothing happened — the difference being whatever could have removed a record in between), and what specific conditions made our result stronger or weaker than average.

A script automates a procedure. An agent skill encodes *judgment*. It tells the agent what each verdict means, how to weigh contradictory signals, and when to say "the evidence doesn't settle it" instead of reaching for a comfortable answer. The rubric explicitly lists "things that are not signals" to prevent the kind of premature conclusions that happen under pressure.

That's not automation. That's a colleague who read the reference material more carefully than you did.

## The Rails community gets it

There's something worth noting about how this was released. The [repository](https://github.com/rails/rails-forensics-CVE-2026-66066) deliberately omits payload generation tools. As the README puts it: "What is withheld is a working generator that could be retargeted at an arbitrary path by changing one argument." The test suite synthesizes the 128 bytes of header it needs in pure Ruby — enough to validate the detector without shipping a weaponizable fixture builder.

The detector was validated against test files from the researchers who reported the vulnerability — [Ethiack](https://ethiack.com/info-hub/research/kindarails2shell-rails-rce-cve) and [Flatt Security](https://blog.flatt.tech/entry/kindarails2shell_rails) — so you know it catches what the real thing looks like. And the README is refreshingly honest about scope: "A clean result from this tooling is strong evidence, not proof. Both skills say so, repeatedly and specifically, and the analysis template has a limits section that is not optional."

Full technical disclosure is planned for [August 28, 2026](https://discuss.rubyonrails.org/t/cve-2026-66066-possible-arbitrary-file-read-and-remote-code-execution-in-active-storage-variant-processing/91432). The forensic toolkit shipped ahead of it because the question "was I exploited?" can't wait for a writeup.

A runbook would have gone stale by the time I opened it. An agent skill adapted — to our database, our storage configuration, our deployment model, our specific code paths. When the agent found that our app soft-deletes Active Storage records, it patched the scanner's queries, explained why the patch was asymmetric, and documented the reasoning. Try getting that from a wiki page.

## What this changes

We're at the beginning of something here. A vulnerability disclosure that ships with:

1. The patch (Rails 7.2.3.2, 8.0.5.1, 8.1.3.1)
2. A detection scanner (single self-contained file, no build dependencies)
3. Agent skills that guide a full forensic investigation
4. Reference material that encodes expert judgment about determination

That's a fundamentally different response package than "here's the CVE, here's the patch, good luck figuring out if you were exploited."

The Rails team — and specifically the people behind this toolkit — have set a standard that I hope other frameworks follow. Not every CVE warrants this level of tooling. But for the ones that do — the ones where "was I exploited?" is a question with real consequences — this is what the answer should look like.

As a CISO, the scariest part of a critical CVE isn't patching. Patching is a deploy. The scary part is the days of uncertainty before you know whether you were hit — the window where you're weighing "rotate everything and disrupt the business" against "wait for evidence and risk being wrong." That uncertainty is what this toolkit eliminated, in an afternoon, with an AI agent doing the work.

If you're running a Rails app with Active Storage, go run the scanner. [The toolkit is here.](https://github.com/rails/rails-forensics-CVE-2026-66066) Your AI agent will know what to do with it.

