Shai-Hulud
A self-spreading worm that runs when an infected npm package is installed, steals npm, GitHub and cloud tokens, and publishes infected versions of the victim's other packages. Most infections ran in CI.
How to check your builds →Red team vs blue team
Attackers rarely target your company. They target the package, the Action or the build tool that companies everywhere depend on, then try the exploit on all of them at once. They don't need your dependency list. You do.
A software supply chain attack compromises something a product is built from, such as an open source package, a CI/CD component or the build system, instead of attacking the product directly. Modern software is mostly other people's code: most of it comes from open source packages, and most of those are transitive dependencies that nobody on the team chose. Compromise one widely used component, and you reach every organization that builds on it.
That changes how attackers pick targets. They don't research one company's dependencies. They pick a package or tool with a large footprint, then exploit it everywhere and see what answers.
A flaw in a popular open source package is disclosed, and attackers exploit it in every product that ships the vulnerable version, usually as a transitive dependency nobody chose directly.
An attacker takes over a maintainer account, or a worm steals publishing tokens, and releases a poisoned version of a trusted package. It runs the moment a build installs it, as with the Shai-Hulud npm worm.
A contributor earns trust in an open source project over time, then slips malicious code into a release that downstream distributions and products pick up, as with the xz utils backdoor.
Packages with names close to popular ones catch typos. Dependency confusion goes further: a public package with the same name as a company's internal one gets installed in its place.
A GitHub Action or other build tool is hijacked, and every pipeline that uses it runs the attacker's code with its secrets in reach, as with tj-actions and Codecov.
The build itself is altered, or the artifact is swapped between the build and deployment, so a clean source tree still produces a compromised release, as with SolarWinds.
SolarWinds is the best-known supply chain attack: malicious code inserted at the build and shipped to customers as a normal update. The most recent attacks go after package registries and CI pipelines instead, where one compromised package or Action reaches every build that uses it. Newest first:
A self-spreading worm that runs when an infected npm package is installed, steals npm, GitHub and cloud tokens, and publishes infected versions of the victim's other packages. Most infections ran in CI.
How to check your builds →CISA reported that every version of this popular GitHub Action was compromised for three days, with secrets exposed in workflow logs. Workflows that referenced it by tag ran the attacker's code.
Andres Freund found a backdoor in xz versions 5.6.0 and 5.6.1 after noticing that SSH logins were using too much CPU. It had been introduced into the upstream project itself.
A flaw in Apache Log4j 2 let an attacker who controlled a logged message run arbitrary code, rated CVSS 10.0. The hard part for most teams was finding which applications contained log4j-core, usually as a dependency of a dependency.
Attackers altered the Bash Uploader script that teams ran in their CI pipelines, so that it sent the CI environment, including credentials, tokens and keys, to a server outside Codecov's infrastructure. It ran unnoticed from January 31 until a customer reported it on April 1.
Attackers had long-standing, covert access to the build process for SolarWinds Orion and inserted malicious code into Orion releases 2019.4 through 2020.2.1 HF1, which customers installed as normal updates. CISA issued an emergency directive in response.
Red team vs blue team
Each stage in the attacker's words, and the defense that answers it.
Stage 1 of 3
No need to know the target. Pick the package, and the targets pick themselves.
Red team
You don't need their lockfile. Popular packages sit deep in the transitive tree of almost everything, and most teams can't say which ones they ship. Public clues narrow it down: versions in JavaScript bundles and HTTP headers, public repositories and container images. And whatever their build fetches outside the lockfile, such as the binary a postinstall script downloads or the layers under their base image, their scanner never saw, so nobody is patching it.
Blue team
CRACI sees the traffic coming into every build and records every package and file it pulls in, including the hidden dependencies that lockfile-based tools miss, at every depth of the transitive tree. Your SBOM comes from the build that shipped, so you have the list an attacker never needed, before anyone goes looking.
Build-time SBOMs →Red team
A new advisory tells you exactly which versions are vulnerable. Build the exploit, scan for anything that answers, and let the scan tell you who they are. You start the same day, while their team is still asking "are we affected?" in a Slack thread.
Blue team
CRACI re-evaluates monitored SBOMs continuously against new vulnerability data. Build history shows which builds contained the vulnerable dependency, direct or transitive, so you know where it shipped and for how long.
Are we affected? →Stage 2 of 3
Exploit the flaw before it's patched, or skip the flaw and ride their pipeline.
Red team
AI turns an advisory and a patch diff into a working attack before their security team has triaged the ticket. Disclosure is your starting gun. Their sprint planning is next week.
Blue team
Policy gates can block any build that contains a specific CVE, so the vulnerable version stops shipping the moment you decide, however deep in the tree it sits.
CVE remediation →Red team
Workflows trust tags like @v4, and tags can be moved. Compromise one popular GitHub Action and every workflow that uses it runs your code, with their secrets in reach.
Blue team
CRACI records the GitHub Actions a workflow runs in the build's SBOM, pinned to the commit that actually ran. When an Action is compromised, you can see which builds ran the bad code instead of guessing from a tag.
CI/CD security checklist →Stage 3 of 3
Take the secrets and stay around for more.
Red team
A malicious package release runs its install script inside their build, where the tokens and cloud credentials live. CI runners can usually reach the whole internet, so the secrets leave for your server before anyone knows the package is bad.
Blue team
A default-deny egress policy lets a build reach its package sources and publish targets, and blocks everything else. Malware sending your secrets to an attacker's host is blocked, even as patient zero of an attack nobody has seen before, and you get an alert on the policy violation.
Egress policies →Red team
Long-lived self-hosted runners are a gift: plant something in the workspace or the tool cache and wait for the next job, and the next set of secrets, to come to you.
Blue team
Every CRACI job runs in its own isolated virtual machine, so nothing one job leaves behind is waiting for the next. There is no fleet of runners for you to patch and harden either.
Managed vs self-hosted runners →After disclosure
The attacker doesn't need to know you run the dependency. They try the exploit everywhere and see what answers. Your team needs to know where that dependency shipped. The difference is whether you have to go looking, and there are more races to run every year: see how many CVEs are published each day.
Red team
Blue team, without a build record
Blue team, with CRACI
No single control stops every supply chain attack. Layer these, and each stage above gets harder. For the pipeline side in detail, see the CI/CD pipeline security checklist.
Attackers watch every advisory and try the exploit on everyone the day a vulnerability is disclosed. They never needed your dependency list. You do. CRACI gives your team that list, taken from the build itself, and closes the doors attackers count on: open egress and shared runners.
An attack on the code, tools or services a product is built from, such as an open source package, a CI/CD component or the build system, rather than on the product itself. One compromise reaches every organization that uses the affected component.
The Shai-Hulud worm (since 2025) spread through infected npm packages and stole CI secrets. The tj-actions/changed-files compromise (CVE-2025-30066) hijacked a popular GitHub Action. The xz utils backdoor (CVE-2024-3094) was slipped into an upstream compression library. Log4Shell (CVE-2021-44228) exploited a flaw in a widely used Java logging library. Codecov's Bash Uploader (2021) was altered to send CI credentials to an outside server, and SolarWinds Orion (2020) shipped malicious code inserted at the build.
SolarWinds. In 2020, attackers with covert access to the build process for SolarWinds Orion inserted malicious code into its releases, and customers installed it as a normal update. It showed that a clean source tree is not enough: the build itself has to be trustworthy and verifiable.
Usually not, and they don't need to. Most attacks target a package rather than a company: once an advisory is out, attackers try the exploit against anything that might run a vulnerable version and see what answers. Public clues, such as versions in JavaScript bundles or open-source repositories, help them aim. The asymmetry is that you do need the list: to know whether you are affected, you have to know what every build shipped, including what it fetched outside the lockfile.
No single control stops them all. Keep an SBOM of what every build ships and monitor it against new advisories, pin dependencies and GitHub Actions, block vulnerable versions at the build, restrict what CI jobs can connect to, isolate jobs and limit their secrets, and verify that deployed artifacts are the ones you built.
Commit a lockfile and install from it in CI, and disable install scripts where a build does not need them (npm's --ignore-scripts). Keep a record of which package versions every build actually installed, so you can check quickly when a new wave such as Shai-Hulud is announced. Restrict what CI jobs can connect to, so a malicious install script cannot send your tokens out, and rotate any token an infected version could have reached.
In security exercises the red team plays the attacker and the blue team defends. This page uses the same split: each stage of a supply chain attack in the attacker's words, answered by the defense.
Known ones, yes. A malicious release that has a published CVE, such as the xz backdoor, is flagged like any other vulnerability, and a policy gate can block builds that contain it. CRACI does not analyze package behavior or score reputation, so a brand-new malicious package goes unflagged until an advisory exists. That is what the egress policy is for: with default deny, the build can reach only the hosts it needs, so the package cannot send your secrets out, even in an attack nobody has seen before.