42 RubyGems That Stay Quiet in CI and Open Shells on Developer Machines

Written by the Rafter Team

As of October 7, 2026.
A clean install in CI tells you how a package behaves in CI.
On October 5, one RubyGems account published 42 gems built to look clean there and to attack the developer's machine instead.
A coding agent that runs bundle install on that machine is the target they wait for.
What happened
SafeDep reported that the account reqthrottle_3474 published 42 malicious gems "in about one hour" on October 5.
Most are named for cryptocurrency developers, such as eth-keystore-utils, bip39-wordlist-utils and btc-wallet-tools.
The last 16 are typosquats of real gems, such as bitcion, etheremu.rb, kecack and solaan-ruby.
OSV published MAL-2026-17598 for eth-keystore-utils the same day.
The code runs during gem install or bundle install.
A gem can declare a native extension, and RubyGems runs that extension's extconf.rb with Ruby at install time; for a real extension, that step writes a Makefile.
All 42 gems declare one, and the Ruby code in lib/ is a working decoy.
The checks come first
Before doing anything, each gem looks at where it is. It stops if any of these hold:
- the environment has a CI variable:
CI,GITHUB_ACTIONS,TRAVIS,JENKINS_URL,GITLAB_CIorCODESPACES; - the hostname is empty or
localhost, or containsuvm,firecracker,sandbox,vagrantorfc-vm; - the username starts with
uAand digits, or containssandbox,tester,analysorscanner; - the current directory is under
/tmp/,/var/tmp,/opt/rubygemsor/workspace/.
Then it looks for what SafeDep calls "two signs of a real workstation": uptime over 30 minutes, and a home directory holding .ssh, .gitconfig, .npmrc, .gem/credentials or .bundle.
SafeDep's excerpt reads uptime from /proc/uptime and counts it as zero if the read fails.
macOS has no /proc, so on our reading of that code the uptime check fails there and the payload fires only on Linux: a Linux workstation, a long-lived dev VM or cloud dev box, or WSL.
On a machine that passes, the gem forks into the background so the payload outlives the install, waits 20 to 40 minutes, and then does one of two things.
Eleven gems open a reverse shell to 45.138.12[.]177 on port 8089 or 8090: a connection from the victim back to the attacker that hands over a shell with the installing user's permissions, and reconnects every 30 seconds if dropped.
The other 31 download wgkit.tar.gz from the same host on port 8092 and run wg_install.sh, whose purpose SafeDep could not determine.
For the 11 shell gems, the install finishes with no visible error.
Why a scanner sees nothing
SafeDep spells out what the checks do to automated review:
A scanner can install the gem in a new container, under /tmp, or in a CI job. In each of these cases, the checks fail.
The scanner records a normal install with no network traffic. So does your CI. Neither result is wrong; each describes a machine the gem was built to ignore.
Earlier this year, a stealer carried a prompt injection aimed at AI scanners. These gems make no argument at all. They behave until they land somewhere no scanner runs.
The workstation and the agent on it
Every check above describes a developer's machine: up for hours, a home directory full of SSH keys and registry tokens, no CI variables, not under /tmp.
A coding agent working on that machine runs installs as the developer, in that home directory.
When an agent adds a dependency and runs bundle install, it passes every check the gem makes.
That is why the coding agent is part of the supply chain. The package arrives through the agent's choice, and the payload runs with the developer's access. A pipeline that scans packages in CI and calls them clean has tested the one environment these gems skip.
What to do now
- Check lockfiles and installed gems. SafeDep's write-up lists all 42 names and versions. Search
Gemfile.lockfiles andgem liston developer machines, not only in CI. - Treat a machine that installed one as compromised. SafeDep's advice: "treat the machine and its credentials as compromised." Rotate SSH keys, registry tokens, cloud credentials and anything else reachable from that home directory, from a clean machine.
- Block and hunt the indicators. Deny traffic to
45.138.12[.]177on ports 8089, 8090 and 8092, and look for/tmp/.w1.tgzand/tmp/.w1/on Linux hosts. - Review new gems before an agent installs them. Typosquats like
bitcionandkecackare one keystroke from a real name. Make adding a dependency a step a person approves. - Analyze in an environment that looks real. A sandbox with long uptime, a home directory with decoy dotfiles, and no CI variables sees what these gems do. A fresh container does not.
- Don't treat a green CI run as a verdict on a package. It is evidence about the runner.
Where Rafter fits
Rafter's free CLI installs command-gating hooks for Claude Code and Codex (rafter agent init --with-claude-code or --with-codex).
A require_approval entry in the project's .rafter.yml, such as ^(gem|bundle) install, makes the agent stop and wait for a person before it installs anything.
The hook sees the command, not the Ruby that extconf.rb runs inside it, so the review has to happen before approval.