2026 supply-chain attacks tested against Chainsaw

58 real attacks from 2026. What Chainsaw stops, and what it misses.

Public 2026 supply-chain attacks, tested against production Chainsaw (v0.22.127) on 25 October 2026, including the ones it cannot see.

Result, 25 October 2026

Outcomes across 58 attacks

Outcome Attacks (v0.22.127)
FULL 33 (7 with a policy setting turned on)
PARTIAL 7
CONDITIONAL 1
DETECT 1
NONE 16

Refused by default on day 0, before any malware feed listed the attack: 14 of 58. What each outcome means, and how the attacks were tested, is below the numbers.

Method and reading

What did we test?

We took 58 public software supply-chain attacks from 2026, from the eScan update-server compromise in January to the Tensorlake npm SDK in October, and checked each one against Chainsaw as it runs in production. This run is from 25 October 2026, against production version v0.22.127. It replaces our 19 October 2026 run against v0.22.118.

The list includes attacks no package firewall can see: vendor update servers, browser and IDE extensions, WordPress plugins, CDN scripts and the Arch User Repository. They stay in the count, so the result is not limited to the attacks Chainsaw was built for.

How was each attack tested?

Each attack went through four steps.

  1. Classification. Every attack was read against the code running in production. An attack counts as covered only when its own payload would trigger a defence, not when a defence for that category exists. A separate reviewer then tried to break the result in both directions; it found the #13 correction described below and no regression.
  2. Reproduction tests. Every outcome other than NONE has a test shaped like the attack that drives the real code path, each with a harmless twin that must not trigger. The fixtures are defanged reconstructions from public write-ups, not the real payloads. The run had 88 of these tests, with 0 failures and 0 skips. The open-source chainsaw-core repository publishes 52 attack reproduction tests, in files named attack2026_*_test.go, covering 27 of the 58 attacks; the other tests exercise hosted-product code, which is not open source. Each of those 27 rows in the table below links its public test file, pinned to one commit, and go test ./... from the chainsaw-core repository root runs them; the 7 tests under policy/ skip there, because the seeded policy file they read is not part of the open-source export.
  3. Mutation check. To show the tests would catch a regression, we broke one mechanism at a time and re-ran the suite. Disabling the release hold turned 28 tests red, blinding the malware lookup turned 31 red, raising the hidden-Unicode threshold turned 2 red, and switching off the import-time block turned 7 red.
  4. Production, read-only. All 369 coordinates from the attacks (package versions, images and actions) were looked up in the malware feed loaded by the live proxy, which held 241,816 entries. 326 were listed by exact version, 5 by an all-versions range and 5 by package name only. The other 33 were absent: Docker images, GitHub Actions, Jenkins plugins and attacks with no record.

What do the outcomes mean?

Each attack gets one outcome:

  • FULL: Chainsaw refuses every malicious version. Either it does so by default (a default rule or detector fires on the package’s bytes or metadata, or the release hold lasted longer than the measured gap between publish and the malware feed listing the version), or a policy setting Chainsaw already ships refuses it, such as a rule that requires git dependencies pinned to a commit or the Guard Action in block mode. The table marks the rows that need a setting turned on, and says which one.
  • PARTIAL: refused by default for some versions or part of the window, and served for the rest.
  • CONDITIONAL: only the default release hold applies and the feed has no record, so it is refused only if the registry removed the version within the hold.
  • DETECT: flagged as a warning. Nothing refuses it.
  • NONE: outside every Chainsaw surface, or inside one with no mechanism that stops it.

Day 0 marks an attack refused by default before any malware feed listed it. For defences that run in a repository rather than on the install path, a defence counts as default only if it refuses with the Chainsaw Guard Action as shipped. The Action ships in warn mode, so a pull-request block counts as FULL with a policy setting, not as a refusal by default.

What were the results?

Of the 58 attacks, 33 are FULL (26 by default and 7 with a policy setting turned on), 7 PARTIAL, 1 CONDITIONAL, 1 DETECT and 16 NONE.

Most FULL rows are still “hold, then feed”: Chainsaw holds every new npm and PyPI version for 48 hours by default, 96 hours when a release changes shape, and the malware feed listed the version before the hold ended. Fourteen attacks are refused by default on day 0, before any feed listed them. The hold only protects installs that go through the Chainsaw proxy.

A correction to the 19 October run. The result we published for 19 October 2026 counted 34 FULL, 27 of them by default. That overstated FULL by one. #13 Trivy was graded FULL on its Docker images, but the Trivy Go module github.com/aquasecurity/trivy@v0.69.4 was never refused, so #13 was PARTIAL on 19 October too. Corrected, that run had 33 FULL (26 by default) and 7 PARTIAL. No attack changed outcome between 19 and 25 October, and the same 14 attacks are refused on day 0. The work in between hardened how enforcement holds up rather than what it detects, and the attackers published genuine packages through normal client paths, so this pack does not exercise those fixes.

One new default rule changed what happens under unchanged outcomes. The CISA KEV rule, which refuses known-exploited vulnerabilities, now refuses the Trivy Go module from about day 7, where before nothing refused it. It also adds a late third layer to #31 TanStack, after the hold and the feed, and matches #16 litellm through unrelated CVEs. The KEV catalog lagged these attacks by 7 to 16 days, so it is a backstop, never a day-0 defence. Separately, the malware feed now lists #57, about 778 to 915 hours after the attack, and production refuses it from that listing on.

Where does Chainsaw fall short, and why?

  • CI attacks (#4, #5, #8, #9) need a policy setting. pr-scan blocks a pull request that introduces a risky workflow, an action pinned by tag, an action from an unlisted publisher, or a step that decodes a payload into a shell. But these attacks came through a workflow already merged, a tag the victim already referenced, or a direct push, and pr-scan does not re-check those.
  • In four attacks a new detector fires, but no default rule refuses on it (#14, #32, #56, #57). The malicious-indicator, image-beacon and Defender-tamper detectors fire, but no default rule blocks on them, and the publisher-change rule ships in monitor mode.
  • #25 and #47 rely on the hold and the feed. The import-time check blocks only the shapes that are clearly malicious, because a broader check flagged 25 of the top 260 PyPI packages, including pip, setuptools, numpy and pillow. A bare exec of decoded code only scores, so xinference 2.6.0 and 2.6.1 and mrmustard 0.7.4 rely on the hold and the feed alone. We traded recall for a rule that can be on by default.

What is still open?

Two known gaps:

  • New default rules reach existing organisations only through a manual backfill. On self-hosted installs, organisations created before v0.22.107 do not get the import-time block, which decides #16, #18, #20, #25, #36, #37 and #46. Self-hosted installs need the same backfill for the new CISA KEV rule.
  • The proxy can load GitHub’s advisory feed for Actions, and the query it sends works, but the feed loads lazily and logs nothing on success, so the run could not prove production had loaded it.

What are the limits of this test?

  • We are the vendor. Chain305 makes Chainsaw, and we wrote the tests and judged the outcomes.
  • FULL for “hold, then feed” rows is measured after the fact. The feed listing times are known now; a new attack could take longer to be listed than the hold lasts, as #14, #39 and #56 did.
  • The fixtures are reconstructions. For #51 and #58 the real payload bytes were not kept, so no day-0 claim is made for them. Listing times for versions deleted from their registry use the attack date and are approximate.
  • Some results rest partly on reading code. The intercom-php refusal in #28, the by-digest Docker refusal in #13, the VS Code task wiring in #54 and the server-side byte refusal in #24 are tested for the signal, not end to end.
  • The KEV matches were probed against a local copy of the vulnerability database (dated 26 August 2026), not production’s copy. They depend on that database carrying CVE-2026-33634 and CVE-2026-45321 against those versions. Whether the release hold applies to Go modules at all is unverified.

Per attack

All 58 attacks, misses included

Outcome on 25 October 2026. The source link is one public write-up of the attack; rows whose only source was a catalogue index have none. The test link opens the row's public reproduction test in chainsaw-core.

# Attack Ecosystem or vector Date Outcome What stopped it, or why not
1 eScan antivirus update server compromise Vendor update 2026-01-20 NONE Vendor update channel. No Chainsaw surface.
2 Cline CLI 2.3.0 npm publish token abuse npm / leaked token 2026-02-17 FULL The 48-hour release hold, doubled to 96 hours because 2.3.0 added a new postinstall script, outlasted the malware feed, which listed it 51.8 hours after publish. The offline guard's bundled list includes 2.3.0.
3 QuickLens / ShotBird extension ownership-transfer hijack Browser extension 2026-02-17 NONE Browser extension ownership transfer. Chainsaw has no extension surface.
4 Trivy repo takeover (hackerbot-claw) pull_request_target test GitHub Actions pull_request_target 2026-02-28 FULL policy setting scan-actions flags the pull_request_target pattern as high, and pr-scan blocks a pull request that introduces such a workflow. Both refuse only as a required check with the Guard Action set to block mode (it ships in warn). Neither stops a fork PR against a workflow already merged, which is how Trivy was hit.
5 Xygeni action v5 tag repointed test GitHub Actions 2026-03-03 FULL policy setting pr-scan blocks a pull request that adds this action by tag, or by SHA from a publisher outside the known list, so the re-pointed tag cannot reach a ref added through a gated PR. Refs already in workflows are judged only by scan-actions --fail-on medium. Both need a required check with the Guard Action in block mode.
6 GlassWorm March wave Our teardown test VS Code/OpenVSX extensions, npm, GitHub 2026-03-03 FULL day 0 npm part: the default hidden-Unicode block rule, which covers variation selectors, refuses these versions on day 0. GitHub repos and VS Code/OpenVSX extensions are still not covered.
7 Apifox CDN script compromise Vendor CDN / Electron app 2026-03-04 NONE Vendor CDN script inside a desktop app. No surface.
8 kubernetes-el Pwn Request test GitHub Actions 2026-03-05 FULL policy setting Same as #4 for this checkout shape: a scan-actions finding and a pr-scan block on the PR that introduces the workflow, both as a required check. The exploit ran against a workflow that was already merged.
9 ForceMemo force-push campaign test GitHub account takeover 2026-03-08 FULL policy setting An org policy rule refuses a git install that is not pinned to a full commit SHA. No default rule ships it, and the payload itself is never fetched or inspected.
10 Offside Wallet Theft Factory Firefox add-ons 2026-03-09 NONE Firefox add-ons. No surface.
11 bittensor-wallet 4.0.2 (PyPI) test PyPI 2026-03-15 CONDITIONAL Only the 48-hour hold applies (96 hours on a warn). The backdoor is compiled into a Rust shared library that no byte detector reads, and the production feed has no record, so it is refused only if PyPI removed it within the hold.
12 BuddyBoss backdoored updates WordPress vendor update 2026-03-17 NONE WordPress vendor update channel. No surface.
13 Trivy v0.69.4 + trivy-action/setup-trivy tag hijack (TeamPCP) Our teardown test GitHub Actions / release artifacts 2026-03-19 PARTIAL The Docker proxy refuses aquasec/trivy 0.69.4 to 0.69.6 by tag, and the 15 published digests on any registry, from its built-in seed. The Go module github.com/aquasecurity/trivy@v0.69.4 was never refused before v0.22.127. It is now refused by default from about day 7 by the default CISA KEV rule, because CVE-2026-33634 entered the KEV catalog on 2026-03-26; v0.69.3 and v0.69.5 do not match. The GitHub Actions tag half needs a policy setting, as in #5. Still open: the advisory lists no 0.69.5 linux/amd64 digest, so that pull from GHCR or ECR is served.
14 CanisterWorm (@opengov/form-builder) npm worm 2026-03-21 PARTIAL Hold, then feed: 5 of 6 worm versions were listed within 45 hours, but @opengov/form-builder 0.12.3 was listed about 120 hours after the attack date, past the 96-hour extension. Its hidden ICP canister C2 is flagged as a malicious indicator on first download, which only a non-default rule refuses.
15 Checkmarx tooling compromises (TeamPCP) test GitHub Actions, OpenVSX/VS Code, Docker, Jenkins plugin 2026-03-23 PARTIAL The Docker proxy refuses checkmarx/kics v2.1.20-debian, v2.1.21-debian and v2.1.21 from its built-in seed. Tags that were malicious only during the roughly 28-minute window and then restored clean were served. The GitHub Actions half (kics-github-action, ast-github-action) needs a policy setting, as in #5.
16 LiteLLM 1.82.7/1.82.8 .pth (PyPI, TeamPCP) Our teardown PyPI / AI gateway 2026-03-24 FULL 1.82.8 is refused on first download by the default import-time execution rule: its .pth line launches a child interpreter on a base64-decoded payload. 1.82.7 relies on hold, then feed (listed about 11 hours after the attack date), because a bare exec of decoded code only scores. Both versions also match the default CISA KEV rule through unrelated LiteLLM CVEs added to the catalog from 2026-05-08, which is redundant and late.
17 IoliteLabs Solidity VS Code extensions IDE extension / dormant publisher 2026-03-25 NONE VS Code extension from a dormant publisher. No extension surface.
18 Telnyx 4.87.1/4.87.2 WAV import-time (PyPI, TeamPCP) Our teardown PyPI 2026-03-27 FULL day 0 The default import-time execution rule refuses 4.87.1 and 4.87.2 on first download: a function called at module load starts a child interpreter that runs a base64-decoded payload. Hold, then feed (listed about 4 hours) behind it.
19 axios npm maintainer takeover Our teardown test npm / account takeover 2026-03-31 FULL day 0 The injected dependency plain-crypto-js is refused on day 0 by the default rule on packages released within a day. axios 1.14.1 and 0.30.4 were held 48 hours and listed 2 to 20 hours after publish. The publisher-change signal fires on 1.14.1, but its default rule only monitors.
20 @velora-dex/sdk 9.4.1 Our teardown npm 2026-04-07 FULL day 0 Refused on first download by the new default import-time execution rule: a prepended top-level exec of a base64-decoded payload piped to bash. Hold, then feed (listed 0.3 hours) as backstop.
21 DAEMON Tools trojanized installers Vendor binary (signed) 2026-04-08 NONE Signed vendor installers. No surface.
22 CPUID CPU-Z / HWMonitor download compromise Vendor download 2026-04-09 NONE Vendor download page. No surface.
23 pgserve CanisterSprawl Our teardown npm 2026-04-21 FULL day 0 The default rule on install scripts that fetch remote URLs refuses pgserve on first download. The feed listed all 10 versions within 16 hours.
24 Bitwarden CLI 2026.4.0 Our teardown test npm / GitHub Actions CI 2026-04-22 FULL day 0 Hold, then feed, and day 0 on the bytes: the CLI guard refuses 2026.4.0 with no feed entry, because its setup script fetches Bun and runs a bundled script under it.
25 Xinference 2.6.0-2.6.2 import-time stealer (PyPI) PyPI / AI 2026-04-22 FULL 2.6.2 is refused on day 0 by the default import-time rule (a detached child interpreter fed decoded code). 2.6.0 and 2.6.1 rely on hold, then feed (listed about 22 hours after the attack date): their bare exec of decoded code only scores, so no rule can refuse them on the bytes.
26 elementary-data forged release test GitHub Actions script injection / PyPI+GHCR 2026-04-24 FULL For PyPI installs, hold, then feed (0.23.3 listed about 23 hours). .pth files are scanned, but the real one-line payload most likely reads as advisory, not a block. The repo-side script-injection lint needs a policy setting, as in #4.
27 SAP CAP / mbt npm (Mini Shai-Hulud wave 1) Our teardown npm / stolen token + OIDC trusted publishing abuse 2026-04-29 FULL day 0 The default rule on install scripts that fetch remote URLs refuses mbt and @cap-js on first download, through the obfuscated script on the second hop. The feed listed them almost at once.
28 Mini Shai-Hulud: lightning (PyPI), intercom-client (npm), intercom-php (Packagist) test npm, Packagist, PyPI 2026-04-30 FULL intercom-php 5.0.2 and intercom-client 7.0.4 are refused on first download by the default rule on install scripts that fetch remote URLs; for intercom-php the re-pointed tag also stays pinned to the cached commit, and a warm, pinned repository also refuses the imposter commit's download path. lightning 2.6.2 and 2.6.3 rely on hold, then feed (listed about 17 hours).
29 JDownloader site installer swap Vendor website/CMS 2026-05-06 NONE Vendor website installer swap. No surface.
30 Cemu 2.6 Linux release assets (TeamPCP) GitHub releases 2026-05-07 NONE Raw release assets get ClamAV (off by default, and not measured against this trojan) and an opt-in gate that refuses every raw file, malicious or clean. A blanket deny is not counted, so nothing shown here tells this file apart.
31 TanStack / Mini Shai-Hulud wave 2 (CVE-2026-45321) Our teardown npm + PyPI worm 2026-05-11 FULL Hold, then feed: all 84 @tanstack versions were listed 4.2 to 5.2 hours after publish, and the hold extends to 96 hours on the 3.7x size jump and the new git dependency. A git-dependency block rule, not on by default, would add a day-0 refusal. A late install is now also refused by the default CISA KEV rule: CVE-2026-45321 entered the KEV catalog 16 days after the attack and matches all 84 attacked versions. That layer is redundant and late.
32 node-ipc account recovery via expired domain Our teardown npm 2026-05-14 FULL Hold, then feed: listed about 2.5 hours after publish. The publisher-change signal fires (a dormant co-maintainer's first publish in 21 months), but its default rule only monitors.
33 Megalodon mass CI backdoor test GitHub workflows 2026-05-18 DETECT The decode-into-shell workflow lint matches the exact step, but Megalodon arrived by direct push, which pr-scan never sees. scan-actions on the default branch flags it afterwards; nothing refuses it.
34 Nx Console VS Code ext 18.95.0 (TeamPCP) IDE extension 2026-05-18 NONE IDE extension. No extension surface.
35 ShapedPlugin Pro builds WordPress vendor pipeline 2026-05-18 NONE Commercial WordPress plugin builds. No surface.
36 Microsoft durabletask 1.4.1-1.4.3 (PyPI, TeamPCP) Our teardown PyPI 2026-05-19 FULL day 0 The default import-time execution rule refuses 1.4.1 to 1.4.3 on first download: at import the module downloads a file to /tmp and runs it with python3. Hold, then feed (listed about 17 hours) behind it.
37 laravel-lang Packagist tag rewrite Our teardown Packagist / Git tags 2026-05-22 FULL day 0 The Composer re-point pin keeps the cached commit on every revalidation when a tag is rewritten, and a pinned repository refuses the imposter commit's download path. A first fetch during the window also meets the default import-time rule, because an autoloaded file fetches content and evals it.
38 Miasma npm worm Our teardown npm worm 2026-06-01 FULL day 0 Wave 1 is refused on first download by the default rule on install scripts that fetch remote URLs. The feed listed all 96 versions.
39 IronWorm (Arweave/WeaveDB npm Rust worm) test npm worm / trusted publishing 2026-06-03 PARTIAL Hold, then feed: 33 of 37 versions were listed within 48 hours; 4 were listed about 237 hours after publish, past the 96-hour extension. A block rule on an install script plus a native binary, not on by default, would add day 0.
40 Injective SDK key-derivation hook test npm / contributor account 2026-06-08 FULL Hold, then feed: listed 45.7 hours after publish, just inside the 48-hour hold.
41 onering 1.4.1 build.rs code exfiltration (crates.io) Our teardown crates.io build script 2026-06-10 FULL day 0 The default rule on install scripts that fetch remote URLs refuses onering 1.4.1 on first download: its build.rs runs curl.
42 Atomic Arch AUR adoption abuse AUR 2026-06-11 NONE AUR. No surface.
43 WordPress vendor-side compromises (Awesome Motive CDN, ARVE, BdThemes) Vendor CDN/source 2026-06-12 NONE WordPress vendor CDN and source. No surface.
44 Mastra npm org (easy-day-js dropper) Our teardown test npm / AI framework 2026-06-17 FULL day 0 The dropper easy-day-js is refused on day 0 by the default rule on packages released within a day. The @mastra versions were held and listed 2 to 4 hours after publish.
45 Jscrambler npm releases (Rust infostealer) test npm / stolen credential 2026-07-11 PARTIAL Hold, then feed: 5 of 7 listed versions within 48 hours; 2 plugin versions at about 88 hours, inside the 96-hour extension only if it applies. grunt-jscrambler and jscrambler-webpack-plugin have no feed record.
46 SleeperGem dormant RubyGems hijacks test RubyGems 2026-07-18 PARTIAL Dendreo 1.1.3 and 1.1.4 and fastlane-plugin-run_tests_firebase_testlab 0.3.2 are refused on day 0 by the default import-time rule: the first stage fetches a body over HTTP and hands it to a shell. The four git_credential_manager 2.8.x versions have no modelled payload and no feed record, so they get the hold only.
47 Xanadu mrmustard 0.7.4 (PyPI) PyPI 2026-07-23 FULL Hold, then feed: listed about 38 hours after the attack date (approximate). The import-time beacon only scores, so no import-time rule refuses it, default or custom.
48 Adform tracking script clipper Third-party JS/CDN 2026-07-26 NONE Third-party tracking script. No surface.
49 Joyfill npm beta (DEV#POPPER blockchain-C2 RAT) test npm / DPRK 2026-07-28 FULL Hold, then feed: the @joyfill beta versions were listed about 10 hours after publish (npm also pulled both within the hold).
50 @badzz88/baileys PhantomSub fork test npm 2026-07-30 FULL policy setting The feed listed the fork 149 to 2,500 hours after publish, far past the hold. A git-dependency block rule, not on by default, refuses its git-hosted payload on day 0.
51 ChainDrop / keyv-cacheable worm test npm worm 2026-08-04 FULL Hold, then feed: 14 of 14 versions listed 0.5 to 7.4 hours after publish. The byte checks also cover the loader shapes in the write-ups, but the real payload bytes were not kept, so no day-0 claim is made.
52 arrayref/internment/append-only-vec + proc-macro1 typosquat dep (crates.io) Our teardown test crates.io / typosquat dep 2026-08-20 FULL day 0 proc-macro1 1.0.107 is refused on day 0 by the default typosquat rule, and its build.rs network connection also trips the rule on install scripts that fetch remote URLs. Either refusal fails the build of arrayref, internment and append-only-vec.
53 @7nohe/openapi-react-query-codegen comment trigger test GitHub Actions comment trigger 2026-08-28 FULL day 0 Hold, then feed (listed about 3 hours), and the CLI guard refuses both waves on the bytes. The repo-side comment-trigger lint needs a policy setting, as in #4.
54 BindsNET forged merge commit (VS Code folder-open task) test Git / VS Code tasks 2026-08-29 FULL policy setting scan-repo flags the folder-open VS Code task, and pr-scan blocks a pull request that introduces one, which matches the Dependabot PR that carried the forged tree. It refuses only with the Guard Action in block mode as a required check, and developers who opened any of the 20 force-pushed branches were exposed regardless.
55 Coder module registry Terraform registry / Cloudflare 2026-08-31 NONE Terraform module registry. Not a format Chainsaw proxies.
56 helpersutils-dev-tools beacon test npm 2026-09-02 PARTIAL The default rule on packages released within a day refuses it for its first 48 hours. Its image beacon stretches each version's hold to 96 hours, but the feed listed it about 480 hours after publish, so it was served from about day 4 to day 20. The CLI guard refuses the beacon bytes only when byte scanning is on.
57 @apexacc/cli Defender / Smart App Control tamper test npm / AI tool 2026-09-17 FULL policy setting The Defender-tamper detector fires on both 1.5.121 and 1.5.122, scoring a warn and a 96-hour hold. A trust-score block rule, not on by default, refuses both on day 0. The malware feed listed the whole package (MAL-2026-17924) about 778 to 915 hours after the attack, between 19 and 25 October 2026, and production refuses both versions from that listing on.
58 Tensorlake npm SDK (Shai-Hulud / ChainDrop toolkit) test npm worm / AI SDK 2026-10-08 FULL Hold, then feed: listed 1.7 hours after publish. As with #51, the real payload bytes were not kept, so no day-0 claim is made.

Check it on your own dependencies

See what Chainsaw would refuse in your builds

Start a free org in monitor mode. Nothing is blocked until you switch a rule to enforce.