Safeguard
Software Supply Chain Security

RubyGems strong_password malicious version RCE

In 2019, attackers hijacked the strong_password RubyGems account and shipped a backdoored v0.0.7 that let them eval() code in production Rails apps.

Priya Mehta
DevSecOps Engineer
7 min read

In June 2019, someone hijacked the RubyGems maintainer account for strong_password, a gem used by Rails applications to enforce password-strength rules, and pushed version 0.0.7 — the first release in six months. The new version shipped with no corresponding commits on GitHub, a mismatch that engineer Tute Costa spotted during a routine dependency review. Buried inside was Rack middleware that decoded and eval()'d any cookie ending in ___id, but only when the app was running in production. The backdoor phoned home to a command-and-control domain, smiley.zzz.com.ua, waited a random delay of up to an hour to dodge sandboxes, then pulled a second-stage payload from Pastebin and executed it. The flaw was tracked as CVE-2019-13354. Before it was pulled, the malicious build was downloaded 537 times — a small fraction of the 38,000+ downloads the clean 0.0.6 release had racked up, but enough to land on production Rails servers.

What is the strong_password gem, and why did a password-strength checker become an RCE vector?

strong_password is a small Ruby gem, originally written by developer Brian McManus, that Rails apps pull in as a dependency to validate password complexity at signup or reset time — the kind of utility gem that gets added once and then forgotten. That's exactly what made it a good target: it had thousands of production installs, an inactive maintainer, and no reason for most teams to ever re-audit it. The gem's job (checking whether a password is "strong enough") required zero elevated permissions, but as a Ruby dependency it still ran with the full privileges of the host application. Once the attacker controlled the package, they controlled anything the Rails process could do, including calling eval on attacker-supplied input. The lesson generalizes past this one gem: any dependency, however narrow its stated purpose, executes with your application's full trust.

How did attackers compromise strong_password on RubyGems.org?

Attackers took over the maintainer's RubyGems.org publishing credentials and pushed a new version directly to the registry, bypassing the GitHub repository entirely. Version 0.0.6, the last legitimate release, had been out for roughly six months with no update. When 0.0.7 appeared on RubyGems.org in June 2019, there was no matching tag, commit, or pull request on the project's GitHub page — the release existed only on the package registry. Maintainer Brian McManus confirmed to researcher Tute Costa that he had lost control of the account. This is the classic RubyGems supply-chain pattern also seen in the rest-client and bootstrap-sass incidents from the same era: the registry account, not the source repository, was the weak link, so code review of the GitHub project would never have caught it. Anyone pinning gems by version number rather than verifying provenance against source had no way to detect the swap before installing it.

How did the malicious code achieve remote code execution?

The backdoor achieved RCE by injecting Rack middleware that ran eval() on decoded cookie values in production. Specifically, any cookie whose name ended in the suffix ___id was base64-decoded and passed straight into Ruby's eval, letting an attacker who could set that cookie run arbitrary Ruby code inside the Rails process. The malicious code wrapped every operation in an empty rescue block — a helper method named _! that silently swallowed exceptions — so a failed attack attempt would produce no error, log line, or crash to alert an operator. To avoid triggering CI pipelines, staging environments, or automated malware scanners, the code checked Rails.env.production? before activating and added a random sleep of up to 3,600 seconds. Once live, it reported the infected host's URL to the C2 domain smiley.zzz.com.ua and could fetch follow-on payloads from a public Pastebin URL — turning a password-strength gem into a fully remote-controllable shell on the production server.

How many applications were affected, and how was it caught?

The confirmed exposure was 537 downloads of the malicious 0.0.7 build before RubyGems.org pulled it, against 38,000+ downloads of the legitimate 0.0.6 release that preceded it — meaning the vast majority of strong_password users were never on the compromised version, but every one of those 537 installs was a production Rails app running a live eval-based backdoor. It was caught not by an automated scanner but by Tute Costa, an engineer who noticed the version bump on RubyGems.org didn't correspond to any commit history on GitHub during a manual dependency review. He reported it to the original maintainer and to Rubygems.org security, and version 0.0.7 was yanked within days of the July 9, 2019 public disclosure by Sophos. That detection path — a human manually diffing registry releases against source history — is not something most engineering teams can rely on catching every time, and it's precisely the gap that automated provenance and integrity checks exist to close.

What is CVE-2019-13354, and how should teams remediate it today?

CVE-2019-13354 formally tracks the malicious strong_password 0.0.7 release and its RCE-via-cookie-eval backdoor. Remediation at the time was straightforward: pin to strong_password 0.0.6 or downgrade/upgrade to the cleaned-up 0.0.8 release published after the maintainer regained control, and rotate any secrets or sessions that might have transited a production instance while 0.0.7 was installed. Seven years later, in the context of any Rails codebase still carrying an old Gemfile.lock, the practical remediation is to audit whether 0.0.7 was ever installed (check Gemfile.lock history and gem install logs, not just the current lockfile) and to verify the checksum of any version between 0.0.6 and 0.0.8 against RubyGems.org's published SHA-256 hash before trusting it. Because the compromise happened at the registry layer, a git log review of the GitHub repo alone would not have revealed it — the mismatch only shows up when you compare what's published against what's committed.

Could this happen again, and what does it say about RubyGems' security model?

Yes — and it has, repeatedly, because RubyGems.org's publish model still lets an account holder push a version with no corresponding source commit, no mandatory 2FA enforcement history until 2022, and no default reachability check on install. The same registry-account-takeover pattern hit rest-client (2019), bootstrap-sass (2019), and multiple gems again in later years, and it mirrors what's since played out repeatedly in npm (event-stream, ua-parser-js) and PyPI. RubyGems did add mandatory MFA for maintainers of high-download gems in 2022, which raises the bar for simple credential-stuffing takeovers, but it does nothing to stop a maintainer's own machine or npm-token-equivalent API key from being phished. For any team running Ruby in production, the structural fix isn't hoping the next backdoored gem gets caught by a manual reviewer — it's automating the diff between what a registry serves and what its source repository actually contains, before that version ever reaches a bundle install.

How Safeguard Helps

Safeguard is built for exactly this failure mode: a dependency that looks routine right up until a registry account gets hijacked. Our SBOM generation and ingest track every Gemfile.lock version pinned across your fleet, so when a gem like strong_password 0.0.7 turns out to be malicious, you get an immediate, exact list of which services installed it rather than a manual grep through lockfiles. Reachability analysis then tells you whether the vulnerable code path — in this case, the cookie-eval middleware — is actually loaded and reachable in your running application, cutting through the noise of dependencies that are present but never executed. Griffin AI correlates registry-versus-source anomalies (a new version with no matching commit history, exactly the signal that took days for a human reviewer to catch here) and flags them before your CI pulls the update. When a fix is available, Safeguard opens an auto-fix PR that pins or upgrades the affected gem and updates the lockfile, so remediation ships in minutes instead of waiting for the next manual dependency audit.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.