Mallory pivots from this family to the IOCs, detections, and named campaigns that touch your stack, and pages you when something new lands.
25 distinct techniques documented for this family, organized by ATT&CK tactic.
Using the GitHub GraphQL API with a stolen token, it commits copies of itself directly to repository branches.
attempting to escalate from instance role credentials into broader AWS account access via Session Manager
On May 11, 2026, an attacker published 84 malicious package versions across 42 @tanstack/* npm packages in under six minutes, distributing credential-stealing malware through one of the JavaScript ecosystem's most widely used library families.
Because the repository's OIDC trusted publisher configuration granted trust at the repository level rather than scoped to a specific protected branch and workflow file, the workflow run triggered by that commit was able to request a valid short-lived npm publish token.
During install, the compromised packages execute a script that downloads the Bun runtime from GitHub ( codeload.github.com ), then runs a tanstack_runner.js payload via bun run tanstack_runner.js
In npm projects, malicious code can run automatically during dependency installation through package lifecycle scripts... It includes a malicious JSON configuration file that executes setup.mjs... the loader runs router_init.js, which contains the main obfuscated malware payload.
This commit was then referenced in the optionalDependencies field of every compromised package's package.json: "optionalDependencies": { "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" }
In the SAP wave, the compromised packages added a preinstall hook that ran setup.mjs... This wave uses a slightly different route... That Git dependency contains a prepare script: "scripts" : { "prepare" : "bun run tanstack_runner.js && exit 1" } This is the trick. npm runs lifecycle scripts for Git dependencies during installation. | In the SAP wave, the compromised packages added a preinstall hook that ran setup.mjs... This wave uses a slightly different route... That Git dependency contains a prepare script: "scripts" : { "prepare" : "bun run tanstack_runner.js && exit 1" }
When npm resolves a github: dependency, it clones the referenced commit and runs lifecycle hooks — including prepare — automatically. This caused tanstack_runner.js to execute on every developer machine or CI runner that installed any of the 84 affected packages.
Using the GitHub GraphQL API with a stolen token, it commits copies of itself directly to repository branches.
attempting to escalate from instance role credentials into broader AWS account access via Session Manager
If the variable is not set, the current process immediately forks a detached copy of itself with stdio set to ['ignore','ignore','ignore'] ... The unref() call ensures Node.js does not wait for the child before exiting.
On npm install, the prepare hook from the malicious @tanstack/setup optionalDependencies entry fires bun run tanstack_runner.js && exit 1. | A package.json defining a package named @tanstack/setup with a prepare lifecycle hook: bun run tanstack_runner.js && exit 1
Using the GitHub GraphQL API with a stolen token, it commits copies of itself directly to repository branches.
attempting to escalate from instance role credentials into broader AWS account access via Session Manager
If the variable is not set, the current process immediately forks a detached copy of itself with stdio set to ['ignore','ignore','ignore'] ... The unref() call ensures Node.js does not wait for the child before exiting.
On npm install, the prepare hook from the malicious @tanstack/setup optionalDependencies entry fires bun run tanstack_runner.js && exit 1. | A package.json defining a package named @tanstack/setup with a prepare lifecycle hook: bun run tanstack_runner.js && exit 1
Every compromised package contains a newly added router_init.js — a 2,341,681-byte file, heavily obfuscated using the javascript-obfuscator pattern: string-array rotation, hex-encoded identifier lookups (_0x253b), control-flow flattening inside while(!![]){...} state machines, and dead-code injection.
The trailing exit 1 is deliberate — npm logs the install as a script failure rather than a script success, suppressing attention while the payload has already run.
Using the GitHub GraphQL API with a stolen token, it commits copies of itself directly to repository branches.
attempting to escalate from instance role credentials into broader AWS account access via Session Manager
Before harvesting credentials, the implant profiles the execution environment across three dimensions: CI platform, operating system, and JavaScript runtime. This gating prevents noisy credential requests in environments where they would fail or trigger alerts.
This commit was then referenced in the optionalDependencies field of every compromised package's package.json: "optionalDependencies": { "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" }
In the SAP wave, the compromised packages added a preinstall hook that ran setup.mjs... This wave uses a slightly different route... That Git dependency contains a prepare script: "scripts" : { "prepare" : "bun run tanstack_runner.js && exit 1" } This is the trick. npm runs lifecycle scripts for Git dependencies during installation. | In the SAP wave, the compromised packages added a preinstall hook that ran setup.mjs... This wave uses a slightly different route... That Git dependency contains a prepare script: "scripts" : { "prepare" : "bun run tanstack_runner.js && exit 1" }
When npm resolves a github: dependency, it clones the referenced commit and runs lifecycle hooks — including prepare — automatically. This caused tanstack_runner.js to execute on every developer machine or CI runner that installed any of the 84 affected packages.
In GitHub Actions environments, the payload runs a Python script that scrapes the Runner.Worker process memory via /proc/{pid}/mem to extract every secret, including masked ones.
TanStack maintainer Tanner Linsley said the attacker used an orphaned commit to gain access to the workflow run that stores the OIDC token, effectively bypassing the project’s existing publishing protections.
The payload located the GitHub Actions Runner.Worker process and read its memory via /proc/{pid}/mem to extract the OIDC token. This token was then used to authenticate direct POST requests to the npm registry, bypassing the workflow's own publish step entirely.
Before harvesting credentials, the implant profiles the execution environment across three dimensions: CI platform, operating system, and JavaScript runtime. This gating prevents noisy credential requests in environments where they would fail or trigger alerts.
1 sources tracked across advisories, community write-ups, and news. New activity surfaces here as Mallory finds it.
Match every observed IP, domain, and hash against your live telemetry.
Named campaigns wielding this family, with evidence pinned to each claim.
CVEs this family uses for access and lateral movement.
YARA, Sigma, Snort, and vendor rules, auto-deployed to your SIEM.
Every documented technique, ranked by evidence weight.
Reddit, Mastodon, and CTI community discussion around this family.