If you run an aging Ruby or Rails app, the new universal Marshal RCE chain elttam published is worth an afternoon. It turns a single Marshal.load on untrusted input into command execution on Ruby 4.0.6, with no gems, no app code, and no prior state on disk. Here is what we check first on a legacy estate, in order.
1. Cookie and session serialization. Rails apps that predate 7.0 sometimes still use the Marshal cookie serializer, or got upgraded with the old setting carried forward. If a signing key ever leaked, a Marshal-serialized cookie is a direct path into this chain. Check config.action_dispatch.cookies_serializer. If it reads :marshal or :hybrid, moving it to :json is the first job.
2. The cache store's coder. Rails.cache with a Marshal coder deserializes whatever sits in the cache backend. If anything user-influenced reaches a cache entry, or the Redis or Memcached instance is shared and loosely locked down, the cache is an injection point. Confirm what writes to it and in what format.
3. Background job and queue payloads. This is the one teams forget. Job arguments, message-queue bodies, anything serialized on the way in and deserialized on the way out. If that path ever touched Marshal and any of the input is external, the worker is the target, not the web tier. Trace where the job args come from.
4. Every direct Marshal.load and YAML.unsafe_load. Grep the app and the gems. Look at file uploads, database columns written by another system, imported data, API request bodies. The rule is simple: Marshal.load on anything you did not produce yourself is command execution. Treat it that way and move it to JSON with an explicit schema.
5. "We're not on Ruby 4, so we're fine." You're not. The chain works unchanged as far back as Ruby 3.3, and elttam's point is that its two triggers live in the C internals of Time deserialization and how a Hash rehashes its keys. Those aren't a five-line patch a maintainer can quietly ship. Your Ruby version is not the control here. Whether you deserialize untrusted input is.
None of this is exotic. Every item is ordinary plumbing that predates the app's current owners, which is exactly why it survives audits: nobody wrote it recently, so nobody thinks to look. The fix is boring and durable. Stop handing untrusted bytes to Marshal.load, use a data-only format with a schema, and verify the serializer settings you inherited instead of assuming them.
Remediating aging Ruby and Rails estates is what we do, and this is the kind of pass we run before an agent or a new feature ever touches the code.

