
Agent skill that audits a Rails codebase for CVE-2026-66066 (KindaRails2Shell) — Active Storage + libvips arbitrary file read / RCE, checking Rails and libvips versions and block-untrusted mitigations
An Agent Skill that audits Ruby on Rails repositories for exposure to CVE-2026-66066 (KindaRails2Shell) — the Active Storage libvips variant-processing vulnerability that allows arbitrary file read and, in a Rails application, remote code execution.
The skill is a configuration auditor and remediation aid. It contains no exploit code and does not describe the attack chain.
| CVE | CVE-2026-66066 |
| Severity | Critical, CVSS 9.5 |
| Package | activestorage (RubyGems) |
| Affected | < 7.2.3.2, >= 8.0 < 8.0.5.1, >= 8.1 < 8.1.3.1 |
| Patched | 7.2.3.2, 8.0.5.1, 8.1.3.1 |
| Advisory | GHSA-xr9x-r78c-5hrm |
libvips reads and writes formats through operations, some of which it marks as unfuzzed — unsafe for untrusted content. Active Storage did not disable them, so an attacker who can upload a crafted file can reach them.
An application is exposed only when all four conditions hold:
activestorage is in an affected version rangeconfig.active_storage.variant_processor resolves to :vipsCondition 3 is the discriminating one. The affected range < 7.2.3.2 includes every Rails 6.x release, but 6.x defaults to :mini_magick, so 6.x is exposed only under a non-default configuration, and releases before 6.0 have no variant_processor setting at all. Rails 7.0 and later default to :vips via config.load_defaults 7.0, which is why the common configuration is exposed. Rails 8.1 also accepts :disabled, which makes condition 3 false.
As a Claude Code plugin, through the marketplace bundled in this repository:
/plugin marketplace add paveg/rails-activestorage-vips-audit
/plugin install rails-activestorage-vips-audit@paveg-skills
With the skills CLI, which installs the same skill into Claude Code, Codex, Cursor, Copilot CLI, and other agents:
npx skills add paveg/rails-activestorage-vips-audit
Or manually:
git clone https://github.com/paveg/rails-activestorage-vips-audit.git
cp -r rails-activestorage-vips-audit/skills/rails-activestorage-vips-audit ~/.claude/skills/
~/.agents/skills/ works as a cross-runtime location for Codex, Copilot CLI, and Gemini CLI. Nothing in the skill depends on where it is installed — there are no absolute paths, and collect-evidence.sh resolves everything relative to the application root it is pointed at.
The skill has two modes, and both accept multiple application paths.
report <app>... # read-only audit, one verdict per application
fix <app>... # applies remediation on a branch; never commits or pushes unprompted
Installed as a plugin, each mode is also a command, so the mode does not have to be typed as an argument:
/rails-activestorage-vips-audit:report path/to/app another/app
/rails-activestorage-vips-audit:fix path/to/app
The commands are entry points for you, not for the agent: they are marked so that Claude never fires them on its own. Claude still invokes the skill itself when a request matches it, which is why fix cannot start without a human asking for it.
report is the default. fix requires a report verdict for the same application first, because the correct remediation depends on it — Rails 6.x, 7.0.x, and 7.1.x have no same-series patch, so there fix applies the interim mitigation and reports that a framework upgrade is required rather than attempting one.
Evidence collection can also be run on its own:
skills/rails-activestorage-vips-audit/scripts/collect-evidence.sh path/to/app another/app
The script gathers facts and deliberately contains no verdict logic. Pass an application root, not an undifferentiated monorepo root. For a monorepo, pass each deployable Rails directory with its own Gemfile.lock and config/application.rb as a separate argument. The collector excludes those nested application roots from a parent's evidence stream, while retaining nested lockfile roots that may be mounted engines or path dependencies.
vips --version where the application runs. Reports keep this under runtime readiness and remediation rather than making the exposure verdict conditional.VIPS_BLOCK_UNTRUSTED in a Dockerfile does not prove the deployed environment sets it. The skill caps such findings at "interim mitigation present, upgrade still required" and never lets one produce a clean verdict.Upgrade to 7.2.3.2, 8.0.5.1, or 8.1.3.1, matching your series. Confirm runtime libvips >= 8.13 and ruby-vips >= 2.2.1 first: where ruby-vips is installed, the patched Active Storage raises during boot unless both hold, and that check applies to :mini_magick applications too.
Interim mitigations, neither of which is a substitute for the upgrade:
VIPS_BLOCK_UNTRUSTED environment variable (libvips >= 8.13; no gem change needed)Vips.block_untrusted(true) from an initializer (ruby-vips >= 2.2.1, plus libvips >= 8.13)On ruby-vips < 2.2.1 the initializer route calls a method that does not exist yet, so raise the gem first or take the environment-variable route, which does not depend on it.
If an application was exposed, treat every secret readable by the application process as compromised and rotate it: secret_key_base, the master key, Active Storage service credentials, database credentials, and third-party tokens.
A WAF is not a mitigation here. Whether it can see the payload depends on the storage service and upload method, so its effectiveness is too configuration-dependent to rely on.
The vulnerability was found and reported independently by RyotaK of GMO Flatt Security and by a team from Ethiack consisting of André Baptista, Bruno Mendes, and Castilho, then disclosed in coordination with the Rails maintainers.