Table of contents

199 RubyGems, two techniques, zero working payloads: Inside a cryptomining campaign that never ran

199 RubyGems, two techniques, zero working payloads: Inside a cryptomining campaign that never ran - Inside Autogenerated RubyGems Squats

Mend.io’s research team caught this campaign before most of the open source community ever saw it. Continuous monitoring of RubyGems flagged a batch of gems that looked, at a glance, like an ordinary cryptomining squat, and Mend.io reported the full batch to RubyGems for takedown. Every gem was pulled within hours.

Mend.io’s team also pulled two of the samples apart in full, because knowing a campaign exists isn’t the same as knowing how it works. What that deeper look found: real tradecraft wrapped around code that, in both samples examined, doesn’t run at all.

The short version: 199 gems, two accounts, one shared name pool. 181 names are machine-generated nonsense that nobody would ever type into a Gemfile. The other 19 are typosquats of a fully resolved dependency tree, including aws-sdk-core and all four of its direct dependencies. Both payloads examined fail with a SyntaxError on require.

Two concealment techniques, both broken

aws-sdk-c0re swaps a zero for the letter o in aws-sdk-core, a substitution built to fool a human eye, not a typo. Its metadata points to the real gem’s homepage and copies its description, while quietly shipping an empty dependency list, a reliable tell on its own. The payload hides in a dotfile (lib/.threadpool.rb) that standard directory scans skip by default. Unpacked, it’s a full XMRig cryptominer launcher: GitHub release lookup, download, and an exec call that would replace the host process outright.

It never got the chance. The file declares module aws_sdk_c0re, a lowercase module name, which is invalid Ruby. Since Ruby parses an entire file before running any of it, the payload’s require call never executes. The malware is stopped by its own version constant, at the exact moment it was built to fire.

piko-max-tool uses a different trick: it ships a complete, legitimate copy of the dry-rails gem, then appends a payload to one of its real files. This one is far more capable, a five-hour dormancy period, checks for seven hypervisors and six container runtimes, load-aware throttling, layered wallet encryption, and worming behavior that reads SSH configs to spread to other hosts.

And it fails on a missing operator:

ENV["DISPLAY"]ENV["WAYLAND_DISPLAY"]ENV["SSH_CONNECTION"]||Thread.exit

No operator between the three lookups. Syntax error, caught before execution. Even a hand-repaired version would still fail: it fetches its miner binary from a URL that serves source, not release binaries, so the download 404s by design.

The naming pattern points to one pipeline

181 of the 200 names come from a fixed vocabulary: ten prefixes, ten adjectives, ten suffixes, combined into names like nano-safe-sys and giga-clean-box. Two accounts drew from that same thousand-name pool and never collided once. Statistically, zero collisions by chance is about one in 4,700, strong evidence of a single shared, deduplicated pipeline behind both accounts rather than two independent operators.

The nonsense-named gems also work as an accidental control group: they picked up roughly 126 downloads apiece from pure registry-mirror traffic. The 19 typosquats picked up nearly thirty times that rate, consistent with automated scanning tuned to this exact pattern.

Key takeaways

199 gems went up, and every one was gone within hours, because Mend.io’s research team caught the batch early and reported it before it could spread beyond a handful of automated pulls. That’s the point of continuous registry monitoring: this kind of campaign is built to look ordinary at every layer a human or scanner might glance at, and the only way to catch it before it works is watching the registry itself, not waiting on a developer to report it.

Strip out the 181 names nobody would ever type, and the real attack surface was 19 gems, at least one of which doesn’t execute at all. Whatever produced this pair of samples understood Ruby’s dependency graph well enough to walk aws-sdk-core down to aws-partitions, and understood sandbox evasion well enough to check for seven different hypervisors, but in both cases didn’t know that module requires a constant name. Generating malicious code has gotten close to free. The one quality gate left standing between a generator and a working exploit is a human running the file once before publishing it, and in this campaign, nobody was standing at that gate.

Manage open source risk

Read the report

Recent resources

199 RubyGems, two techniques, zero working payloads: Inside a cryptomining campaign that never ran - Runtime Protection

Runtime protection: Threats, technologies, and 5 best practices

What runtime protection is, key threats, and 5 best practices to apply.

Read more
199 RubyGems, two techniques, zero working payloads: Inside a cryptomining campaign that never ran - Blog best software composition analysis services

Best Software Composition Analysis Services: Top 8 in 2026

Compare the top 8 software composition analysis services of 2026.

Read more
199 RubyGems, two techniques, zero working payloads: Inside a cryptomining campaign that never ran - Blog cover Top 8 AST providers post

Best Application Security Testing Providers: Top 8 in 2026

The top 8 application security testing providers to know in 2026.

Read more