{"vulnerability": "CVE-2026-45321", "sightings": [{"uuid": "99e0f5f1-7333-4ed5-9f75-b286b60b4a73", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://bsky.app/profile/dameyiwu.bsky.social/post/3mlnrlvzty22x", "content": "\"The #TanStack supply chain compromise has been assigned the #CVE identifier CVE-2026-45321. It carries a #CVSS score of 9.6 out of a maximum of 10.0, indicating critical severity. The incident has impacted 42 packages and 84 versions across the TanStack ecosystem.\"", "creation_timestamp": "2026-05-12T12:20:35.880944Z"}, {"uuid": "46bbd99e-aef6-4fce-8827-e87109ac984e", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://t.me/GithubRedTeam/83987", "content": "\ud83d\udea8 GitHub \u76d1\u63a7\u6d88\u606f\u63d0\u9192\n\n\ud83d\udea8 \u53d1\u73b0\u5173\u952e\u8bcd\uff1a #CVE-2026\n\n\ud83d\udce6 \u9879\u76ee\u540d\u79f0\uff1a tanstack-shield\n\ud83d\udc64 \u9879\u76ee\u4f5c\u8005\uff1a Caixa-git\n\ud83d\udee0 \u5f00\u53d1\u8bed\u8a00\uff1a Python\n\u2b50 Star\u6570\u91cf\uff1a 0  |  \ud83c\udf74 Fork\u6570\u91cf\uff1a 0\n\ud83d\udcc5 \u66f4\u65b0\u65f6\u95f4\uff1a 2026-05-12 19:59:44\n\n\ud83d\udcdd \u9879\u76ee\u63cf\u8ff0\uff1a\n\ud83d\udee1\ufe0f One-command scanner for CVE-2026-45321 \u2014 TanStack npm supply-chain attack\n\n\ud83d\udd17 \u70b9\u51fb\u8bbf\u95ee\u9879\u76ee\u5730\u5740", "creation_timestamp": "2026-05-12T20:00:04.000000Z"}, {"uuid": "a5bc3ce6-994a-4fc8-a26a-8087c190a8f2", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://t.me/GithubRedTeam/84001", "content": "\ud83d\udea8 GitHub \u76d1\u63a7\u6d88\u606f\u63d0\u9192\n\n\ud83d\udea8 \u53d1\u73b0\u5173\u952e\u8bcd\uff1a #CVE-2026\n\n\ud83d\udce6 \u9879\u76ee\u540d\u79f0\uff1a scan-shai-hulud\n\ud83d\udc64 \u9879\u76ee\u4f5c\u8005\uff1a qi-scape\n\ud83d\udee0 \u5f00\u53d1\u8bed\u8a00\uff1a Shell\n\u2b50 Star\u6570\u91cf\uff1a 0  |  \ud83c\udf74 Fork\u6570\u91cf\uff1a 0\n\ud83d\udcc5 \u66f4\u65b0\u65f6\u95f4\uff1a 2026-05-12 22:41:35\n\n\ud83d\udcdd \u9879\u76ee\u63cf\u8ff0\uff1a\nDetect CVE-2026-45321 Mini Shai-Hulud supply chain compromise \u2014 scans for 170 npm + 2 PyPI poisoned packages across TanStack, Mistral AI, UiPath, OpenSearch, Guardrails AI\n\n\ud83d\udd17 \u70b9\u51fb\u8bbf\u95ee\u9879\u76ee\u5730\u5740", "creation_timestamp": "2026-05-12T23:00:04.000000Z"}, {"uuid": "51d30509-47f9-494e-bb51-5bfbf746e5ce", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://t.me/GithubRedTeam/84005", "content": "\ud83d\udea8 GitHub \u76d1\u63a7\u6d88\u606f\u63d0\u9192\n\n\ud83d\udea8 \u53d1\u73b0\u5173\u952e\u8bcd\uff1a #CVE-2026\n\n\ud83d\udce6 \u9879\u76ee\u540d\u79f0\uff1a shai-hulud-scan\n\ud83d\udc64 \u9879\u76ee\u4f5c\u8005\uff1a shayr1\n\ud83d\udee0 \u5f00\u53d1\u8bed\u8a00\uff1a None\n\u2b50 Star\u6570\u91cf\uff1a 0  |  \ud83c\udf74 Fork\u6570\u91cf\uff1a 0\n\ud83d\udcc5 \u66f4\u65b0\u65f6\u95f4\uff1a 2026-05-12 23:55:00\n\n\ud83d\udcdd \u9879\u76ee\u63cf\u8ff0\uff1a\nClaude Code skill to scan machines for Mini Shai-Hulud (CVE-2026-45321) supply chain worm IOCs\n\n\ud83d\udd17 \u70b9\u51fb\u8bbf\u95ee\u9879\u76ee\u5730\u5740", "creation_timestamp": "2026-05-13T00:00:04.000000Z"}, {"uuid": "ddfc3668-f91c-4bde-ac19-48a6e7917961", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://t.me/true_secator/8195", "content": "\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044f\u0441\u044c \u043a TeamPCP, \u043f\u0440\u043e\u0432\u0435\u0440\u043d\u0443\u0432\u0448\u0435\u0439 \u0441\u0435\u0440\u0438\u044e \u0430\u0442\u0430\u043a \u043d\u0430 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u043f\u043e\u0441\u0442\u0430\u0432\u043e\u043a, \u043f\u043e\u0441\u043b\u0443\u0436\u043d\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u0433\u0440\u0443\u043f\u043f\u044b \u043f\u043e\u043f\u043e\u043b\u043d\u0438\u043b\u0441\u044f \u043d\u043e\u0432\u043e\u0439 \u043a\u0430\u043c\u043f\u0430\u043d\u0438\u0435\u0439 Mini Shai-Hulud, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0439 \u0441 \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0430\u0446\u0438\u0435\u0439 \u043f\u0430\u043a\u0435\u0442\u043e\u0432 npm \u0438 PyPI \u043e\u0442 TanStack, UiPath, Mistral AI, OpenSearch \u0438 Guardrails AI.\n\n\u0412 \u0437\u0430\u0442\u0440\u043e\u043d\u0443\u0442\u044b\u0435 npm-\u043f\u0430\u043a\u0435\u0442\u044b \u0431\u044b\u043b\u0438 \u0432\u043d\u0435\u0441\u0435\u043d\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0438\u0435 \u043e\u0431\u0444\u0443\u0441\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 JavaScript-\u0444\u0430\u0439\u043b (router_init.js), \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0439 \u0434\u043b\u044f \u043f\u0440\u043e\u0444\u0438\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0440\u0435\u0434\u044b \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u043e\u0433\u043e \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430 \u043a\u0440\u0430\u0436\u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u043e\u0432, \u043d\u0430\u0446\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043d\u0430 \u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0445 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u043e\u0432, \u043a\u0440\u0438\u043f\u0442\u043e\u043a\u043e\u0448\u0435\u043b\u044c\u043a\u0438, \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b \u0418\u0418, \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u044b \u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u044b CI. \u0414\u0430\u043d\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f \u043d\u0430 filev2.getsession[.]org.\n\n\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 \u0441\u0435\u0441\u0441\u0438\u0439 (SPS) - \u044d\u0442\u043e \u043f\u0440\u0435\u0434\u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u043e\u0432 \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0434\u043e\u043c\u0435\u043d \u0432\u0440\u044f\u0434 \u043b\u0438 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d \u0432 \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u0441\u0440\u0435\u0434\u0430\u0445, \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044f, \u0447\u0442\u043e \u043e\u043d \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u0434\u0435\u0446\u0435\u043d\u0442\u0440\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0441\u043b\u0443\u0436\u0431\u0435 \u043e\u0431\u043c\u0435\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438.\n\n\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0437\u0430\u043f\u0430\u0441\u043d\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u0435\u043c\u044b\u0445 \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u0430\u043c\u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f\u0445 \u043f\u043e\u0434 \u0438\u043c\u0435\u043d\u0435\u043c \u0430\u0432\u0442\u043e\u0440\u0430 claude@users.noreply.github.com \u0447\u0435\u0440\u0435\u0437 API GitHub GraphQL \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0443\u043a\u0440\u0430\u0434\u0435\u043d\u043d\u044b\u0445 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 GitHub.\n\n\u0412\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u0430\u044f \u041f\u041e \u0442\u0430\u043a\u0436\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u0430 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438 \u0432 Claude Code \u0438 Microsoft Visual Studio Code (VS Code), \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u044f \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044f \u0441\u0442\u0438\u043b\u0435\u0440 \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u044d\u0442\u0438\u0445 IDE.\n\n\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u0441\u043b\u0443\u0436\u0431\u0443 gh-token-monitor \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0439 \u043a\u0440\u0430\u0436\u0438 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 GitHub, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0432\u043d\u0435\u0434\u0440\u044f\u0435\u0442 \u0434\u0432\u0430 \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u044b\u0445 \u0440\u0430\u0431\u043e\u0447\u0438\u0445 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 GitHub Actions \u0434\u043b\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u043e\u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0432 \u043e\u0431\u044a\u0435\u043a\u0442 JSON \u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 api.masscan[.]cloud.\u00a0\n\nTanStack \u0432\u044b\u044f\u0441\u043d\u0438\u043b\u0430, \u0447\u0442\u043e \u0432\u0437\u043b\u043e\u043c \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u0435\u043b \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0430\u0442\u0430\u043a \u043d\u0430 GitHub Actions, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0435\u0439 \u0442\u0440\u0438\u0433\u0433\u0435\u0440 pull_request_target, \u043e\u0442\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u044d\u0448\u0430 GitHub Actions \u0438 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 OIDC \u0438\u0437 \u043f\u0430\u043c\u044f\u0442\u0438 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0438\u0437 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 GitHub Actions.\n\n\u041a\u0430\u043a \u0437\u0430\u044f\u0432\u043b\u044f\u0435\u0442 TanStack, \u043d\u0438\u043a\u0430\u043a\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u044b npm \u043d\u0435 \u0431\u044b\u043b\u0438 \u0443\u043a\u0440\u0430\u0434\u0435\u043d\u044b, \u0438 \u0441\u0430\u043c \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 npm \u043d\u0435 \u0431\u044b\u043b \u0441\u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0438\u0440\u043e\u0432\u0430\u043d.\n\n\u0412 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438, \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0437\u043b\u043e\u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u0438\u043a\u0438 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043b\u0438 \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u0443\u044e \u043f\u043e\u043b\u0435\u0437\u043d\u0443\u044e \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0432 \u0444\u043e\u0440\u043a\u0435 GitHub, \u0432\u043d\u0435\u0434\u0440\u0438\u043b\u0438 \u0435\u0435 \u0432 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d\u043d\u044b\u0435 npm-\u0430\u0440\u0445\u0438\u0432\u044b, \u0430 \u0437\u0430\u0442\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u0438\u043b\u0438 \u043b\u0435\u0433\u0438\u0442\u0438\u043c\u043d\u044b\u0439 \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 TanStack/router \u0434\u043b\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0441\u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0432\u0435\u0440\u0441\u0438\u0439 \u0441 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435\u043c SLSA.\u00a0\n\n\u041e\u0442\u043b\u0438\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u044c\u044e \u043d\u043e\u0432\u043e\u0433\u043e \u0447\u0435\u0440\u0432\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0435\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u044f\u0442\u044c\u0441\u044f \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u043f\u0430\u043a\u0435\u0442\u044b, \u043d\u0430\u0445\u043e\u0434\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 \u0434\u043b\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d npm \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c bypass_2fa, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u043c \u0432 true, \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u044f\u044f \u0432\u0441\u0435 \u043f\u0430\u043a\u0435\u0442\u044b, \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043e\u0434\u043d\u0438\u043c \u0438 \u0442\u0435\u043c \u0436\u0435 \u0441\u043e\u043f\u0440\u043e\u0432\u043e\u0436\u0434\u0430\u044e\u0449\u0438\u043c, \u0438 \u043e\u0431\u043c\u0435\u043d\u0438\u0432\u0430\u044f \u0442\u043e\u043a\u0435\u043d GitHub OIDC \u043d\u0430 \u0442\u043e\u043a\u0435\u043d \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0431\u043e\u0439\u0442\u0438 \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u0443\u044e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e.\n\n\u0423\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 \u0446\u0435\u043f\u043e\u0447\u043a\u0435 \u043f\u043e\u0441\u0442\u0430\u0432\u043e\u043a TanStack \u043f\u0440\u0438\u0441\u0432\u043e\u0435\u043d\u00a0CVE-2026-45321 (CVSS 9,6). \u0418\u043d\u0446\u0438\u0434\u0435\u043d\u0442 \u0437\u0430\u0442\u0440\u043e\u043d\u0443\u043b 42 \u043f\u0430\u043a\u0435\u0442\u0430 \u0438 84 \u0432\u0435\u0440\u0441\u0438\u0438 \u0432 \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u0435 TanStack.\n\n\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0430\u0442\u0430\u043a\u0438 \u0431\u044b\u043b\u0438 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d\u044b \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0432\u0435\u0439\u0435\u0440 \u0432\u044b\u043f\u0443\u0441\u043a\u0430 GitHub Actions \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0443\u043a\u0440\u0430\u0434\u0435\u043d\u043d\u044b\u0445 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 OIDC.\n\n\u0412 \u043a\u0440\u0430\u0439\u043d\u0435 \u0440\u0435\u0434\u043a\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0441\u043a\u0430\u043b\u0430\u0446\u0438\u0438 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0438 \u0441\u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043f\u0430\u043a\u0435\u0442\u044b \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0430\u0442\u0442\u0435\u0441\u0442\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f SLSA Build Level 3, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u044d\u0442\u043e\u0442 \u0447\u0435\u0440\u0432\u044c npm \u043f\u0435\u0440\u0432\u044b\u043c \u0437\u0430\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0441\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u044b\u0445 \u043f\u0430\u043a\u0435\u0442\u043e\u0432 \u0441 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c\u0438 \u0430\u0442\u0442\u0435\u0441\u0442\u0430\u0446\u0438\u044f\u043c\u0438.\n\n\u041a \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u043c\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 Mini Shai-Hulud \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0438\u043b\u0441\u044f \u0437\u0430 \u043f\u0440\u0435\u0434\u0435\u043b\u044b TanStack \u043d\u0430 \u043f\u0430\u043a\u0435\u0442\u044b \u043e\u0442 UiPath, DraftLab \u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0437 PyPI.\n\n\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442, \u0447\u0442\u043e \u043a\u0430\u043c\u043f\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u0442 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u044f\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u043f\u043e npm, \u0442\u0430\u043a \u0438 \u043f\u043e PyPI, \u0437\u0430\u0442\u0440\u0430\u0433\u0438\u0432\u0430\u044f \u043f\u0430\u043a\u0435\u0442\u044b, \u043e\u0442\u043d\u043e\u0441\u044f\u0449\u0438\u0435\u0441\u044f \u043a \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0439 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435, \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c \u0418\u0418, \u043f\u0430\u043a\u0435\u0442\u0430\u043c \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u043c \u0441 \u0430\u0432\u0438\u0430\u0446\u0438\u0435\u0439, \u043a\u043e\u0440\u043f\u043e\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0435\u0439, \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438 \u0434\u043b\u044f \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u0438 \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u0430\u043c, \u0441\u043c\u0435\u0436\u043d\u044b\u043c \u0441 CI/CD.\n\n\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u0432 \u043e\u0442\u0447\u0435\u0442\u0430\u0445 - Aikido Security, Endor Labs, SafeDep, Socket \u0438\u00a0StepSecurity.", "creation_timestamp": "2026-05-12T18:40:06.000000Z"}, {"uuid": "a6c609c3-7ed5-40af-99ce-3d81656edffd", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://bsky.app/profile/securitylab-jp.bsky.social/post/3mlredjogec2o", "content": "TanStack\u306enpm\u30d1\u30c3\u30b1\u30fc\u30b842\u4ef6\u306b\u30b5\u30a4\u30d0\u30fc\u653b\u6483\u3067\u30de\u30eb\u30a6\u30a7\u30a2\u304c\u6df7\u5165\u3001GitHub Actions \u7d4c\u7531\u3067\u8a8d\u8a3c\u60c5\u5831\u7a83\u53d6\u306e\u6050\u308c(CVE-2026-45321)\nrocket-boys.co.jp/security-mea...\n\n#\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u5bfe\u7b56Lab #security #DataBreach #securitynews", "creation_timestamp": "2026-05-13T22:33:55.816318Z"}, {"uuid": "02d47c55-9fae-4960-9092-c14c3740bdc8", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "Telegram/cc4rNasGAf6eBRoD2kZTJIxW5lr85DmN8AZG7mw1GXsUUwc", "content": "", "creation_timestamp": "2026-05-13T15:00:06.000000Z"}, {"uuid": "87ffda8e-b563-445a-a342-fe6c99b7938b", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "Telegram/88YkBdmMMIAUjkN-cy3WAm2Yboedxaf0GfTYOA3KafU0qTc", "content": "", "creation_timestamp": "2026-05-12T15:00:07.000000Z"}, {"uuid": "c486161f-b0e1-4fec-a0d8-20f95a422ec4", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://t.me/bdufstecru/3161", "content": "\u0423\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u044c \u043d\u0430\u0431\u043e\u0440\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a TanStack \u0441\u0432\u044f\u0437\u0430\u043d\u0430 \u0441 \u043d\u0430\u043b\u0438\u0447\u0438\u0435\u043c \u043d\u0435\u0434\u0435\u043a\u043b\u0430\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439. \u042d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0430\u0446\u0438\u044f \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0438 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u044c \u043d\u0430\u0440\u0443\u0448\u0438\u0442\u0435\u043b\u044e, \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u043c\u0443 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434\n\nBDU:2026-06725\nCVE-2026-45321\n\n\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044f:\nhttps://github.com/TanStack/router/security/advisories/GHSA-g7cv-rxg3-hmpx\nhttps://github.com/TanStack/router/issues/7383", "creation_timestamp": "2026-05-15T12:57:17.000000Z"}, {"uuid": "1e37d5b1-7d34-4a3c-9883-e1424609d277", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://bsky.app/profile/cyberhub.blog/post/3mltfvh2rkc2w", "content": "\ud83d\udccc CVE-2026-45321 - On 2026-05-11, between approximately 19:20 and 19:26 UTC, 84 malicious versions across 42 @tanstack/* packages were published to the npm registry. The... https://www.cyberhub.blog/cves/CVE-2026-45321", "creation_timestamp": "2026-05-14T18:07:18.190501Z"}, {"uuid": "44276c54-115a-4d96-ae16-8908dfad65f7", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "Telegram/4V1L8rm_ci8R4NTF3AvtHqkyJLl9nOzw6nvw-H3zHaDaYho", "content": "", "creation_timestamp": "2026-05-13T03:00:06.000000Z"}, {"uuid": "3703d74a-a6c3-45a0-94d2-2f2046eabd30", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://bsky.app/profile/aicountdown.bsky.social/post/3mluhprwhut2s", "content": "AI Index: 51.6/100 \u25b2 +0.9 (Morning update)\n\nTop driver: OpenAI disclosed a confirmed security breach: two employee devices compromised in the 'Mini Shai-Hulud' supply-chain campaign (CVE-2026-45321, CVSS 9.6) \u2014 th...\n\nFull report \u2192 madad-ai.com\n\n#AI #AISafety", "creation_timestamp": "2026-05-15T04:12:24.419310Z"}, {"uuid": "8919c7a0-7388-4bc4-91a8-3ff60124d6d3", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://gist.github.com/prashanthnatraj/6405cf30127b74b185e049dd2fd746e6", "content": "#!/usr/bin/env bash\n# =============================================================================\n# shai-hulud-detector.sh \u2014 One-click Mini Shai-Hulud detection\n# Built by Lume AI (https://getlumeai.com) \u00b7 CVE-2026-45321 (CVSS 9.6)\n#\n# Why detection-before-rotation matters: the worm installs a persistence\n# daemon (gh-token-monitor) that runs `rm -rf ~` if it detects token\n# revocation. NEVER rotate credentials before confirming the daemon is gone.\n#\n# Usage:  curl -fsSL https://gist.githubusercontent.com/prashanthnatraj/6405cf30127b74b185e049dd2fd746e6/raw/shai-hulud-detector.sh | bash\n#         bash shai-hulud-detector.sh [--dir /path/to/project]\n#\n# Exit codes: 0 = CLEAN  |  1 = SUSPICIOUS  |  2 = INFECTED\n# =============================================================================\n\nset -euo pipefail\n\n# \u2500\u2500 Formatting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nRED='\\033[0;31m'\nYELLOW='\\033[1;33m'\nGREEN='\\033[0;32m'\nCYAN='\\033[0;36m'\nBOLD='\\033[1m'\nRESET='\\033[0m'\n\nbanner() { printf \"\\n${CYAN}${BOLD}%s${RESET}\\n\" \"$1\"; }\nok()     { printf \"  ${GREEN}[CLEAN]${RESET}      %s\\n\" \"$1\"; }\nwarn()   { printf \"  ${YELLOW}[SUSPICIOUS]${RESET} %s\\n\" \"$1\"; }\nhit()    { printf \"  ${RED}[INFECTED]${RESET}   %s\\n\" \"$1\"; }\ninfo()   { printf \"  ${CYAN}[INFO]${RESET}       %s\\n\" \"$1\"; }\nsep()    { printf '%s\\n' \"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\"; }\n\n# \u2500\u2500 State tracking \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nINFECTED=0\nSUSPICIOUS=0\nFINDINGS=()\n\nflag_infected() { INFECTED=1; FINDINGS+=(\"INFECTED: $1\"); }\nflag_suspicious() { SUSPICIOUS=1; FINDINGS+=(\"SUSPICIOUS: $1\"); }\n\n# \u2500\u2500 Argument parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPROJECT_DIR=\"${PWD}\"\nwhile [[ $# -gt 0 ]]; do\n  case \"$1\" in\n    --dir|-d)\n      PROJECT_DIR=\"${2:-}\"\n      shift 2\n      ;;\n    --help|-h)\n      grep '^#' \"$0\" | sed 's/^# \\{0,1\\}//'\n      exit 0\n      ;;\n    *)\n      shift\n      ;;\n  esac\ndone\n\nif [[ ! -d \"$PROJECT_DIR\" ]]; then\n  printf \"${RED}Error:${RESET} Directory not found: %s\\n\" \"$PROJECT_DIR\" &gt;&amp;2\n  exit 1\nfi\n\n# \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nprintf \"\\n\"\nsep\nprintf \"${BOLD}  Mini Shai-Hulud Detector${RESET}  |  CVE-2026-45321  |  Built by Lume AI\\n\"\nsep\nprintf \"  Scanning: %s\\n\" \"$PROJECT_DIR\"\nprintf \"  Date:     %s\\n\" \"$(date)\"\nsep\n\n# \u2500\u2500 Step 1: Lockfile presence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"1/7  Lockfile check\"\n\nLOCKFILE=\"\"\nfor f in \"$PROJECT_DIR/package-lock.json\" \"$PROJECT_DIR/pnpm-lock.yaml\" \"$PROJECT_DIR/yarn.lock\"; do\n  if [[ -f \"$f\" ]]; then\n    LOCKFILE=\"$f\"\n    info \"Found lockfile: $f\"\n    break\n  fi\ndone\n\nif [[ -z \"$LOCKFILE\" ]]; then\n  warn \"No lockfile found (package-lock.json / pnpm-lock.yaml / yarn.lock). Cannot run lockfile checks.\"\n  flag_suspicious \"no lockfile present \u2014 cannot verify dependency tree\"\nfi\n\nPKGJSON=\"$PROJECT_DIR/package.json\"\nif [[ ! -f \"$PKGJSON\" ]]; then\n  warn \"No package.json found at $PROJECT_DIR. Is this an npm project?\"\nfi\n\n# \u2500\u2500 Step 2: @tanstack/setup \u2014 single fastest tell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"2/7  @tanstack/setup (fastest tell)\"\n\nTANSTACK_SETUP_HIT=0\nfor f in \"$PKGJSON\" \"${LOCKFILE:-}\"; do\n  [[ -z \"$f\" || ! -f \"$f\" ]] &amp;&amp; continue\n  if grep -q \"@tanstack/setup\" \"$f\" 2&gt;/dev/null; then\n    hit \"@tanstack/setup found in $f \u2014 this package does NOT exist in the legitimate TanStack org\"\n    flag_infected \"@tanstack/setup present in $f\"\n    TANSTACK_SETUP_HIT=1\n  fi\ndone\n\nif [[ $TANSTACK_SETUP_HIT -eq 0 ]]; then\n  ok \"@tanstack/setup not found in project manifests\"\nfi\n\n# Also scan node_modules if present\nNM=\"$PROJECT_DIR/node_modules\"\nif [[ -d \"$NM/@tanstack/setup\" ]]; then\n  hit \"@tanstack/setup directory exists in node_modules \u2014 malicious package installed\"\n  flag_infected \"node_modules/@tanstack/setup directory present\"\nelif [[ -d \"$NM\" ]]; then\n  ok \"@tanstack/setup not installed in node_modules\"\nfi\n\n# \u2500\u2500 Step 3: Known malicious package namespaces in lockfile \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"3/7  Known malicious namespaces (CVE-2026-45321 IOC list)\"\n\nMALICIOUS_PATTERNS=(\n  \"@tanstack/setup\"\n  \"@uipath/setup\"\n  \"@mistralai/setup\"\n  \"@guardrails-ai/setup\"\n  \"@opensearch-project/setup\"\n  \"@cap-js/setup\"\n)\n\nif [[ -n \"$LOCKFILE\" &amp;&amp; -f \"$LOCKFILE\" ]]; then\n  for pattern in \"${MALICIOUS_PATTERNS[@]}\"; do\n    if grep -q \"$pattern\" \"$LOCKFILE\" 2&gt;/dev/null; then\n      hit \"Lockfile contains known IOC: $pattern\"\n      flag_infected \"lockfile contains IOC package: $pattern\"\n    else\n      ok \"$pattern \u2014 not in lockfile\"\n    fi\n  done\n\n  # Broader namespace sweep (legitimate packages in these namespaces are fine;\n  # this surfaces them for manual review if @tanstack/setup was found above)\n  SUSPICIOUS_NAMESPACES=(\"@uipath\" \"@mistralai\" \"@guardrails-ai\" \"@opensearch-\" \"@cap-js\")\n  if [[ $INFECTED -eq 1 ]]; then\n    banner \"   Extended namespace sweep (infection confirmed \u2014 showing all hits for manual review)\"\n    for ns in \"${SUSPICIOUS_NAMESPACES[@]}\"; do\n      hits=$(grep -c \"$ns\" \"$LOCKFILE\" 2&gt;/dev/null || true)\n      if [[ \"$hits\" -gt 0 ]]; then\n        warn \"$ns \u2014 $hits lockfile reference(s). Verify these are legitimate packages.\"\n      fi\n    done\n  fi\nelse\n  warn \"Skipping lockfile namespace sweep \u2014 no lockfile\"\nfi\n\n# \u2500\u2500 Step 4: Payload files on disk \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"4/7  Payload files (router_init.js / tanstack_runner.js)\"\n\nKNOWN_BAD_HASHES=(\n  \"ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c  router_init.js\"\n  \"2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96  tanstack_runner.js\"\n)\n\nPAYLOAD_FILES=$(find ~ -maxdepth 10 \\( -name \"router_init.js\" -o -name \"tanstack_runner.js\" \\) 2&gt;/dev/null || true)\n\nif [[ -z \"$PAYLOAD_FILES\" ]]; then\n  ok \"No payload files (router_init.js / tanstack_runner.js) found in home directory\"\nelse\n  while IFS= read -r fpath; do\n    [[ -z \"$fpath\" ]] &amp;&amp; continue\n    fname=$(basename \"$fpath\")\n    actual_hash=$(shasum -a 256 \"$fpath\" 2&gt;/dev/null | awk '{print $1}' || true)\n    for known in \"${KNOWN_BAD_HASHES[@]}\"; do\n      expected_hash=$(echo \"$known\" | awk '{print $1}')\n      expected_name=$(echo \"$known\" | awk '{print $2}')\n      if [[ \"$fname\" == \"$expected_name\" &amp;&amp; \"$actual_hash\" == \"$expected_hash\" ]]; then\n        hit \"Payload file with known malicious hash: $fpath\"\n        hit \"  SHA-256: $actual_hash\"\n        flag_infected \"malicious payload file found: $fpath\"\n      fi\n    done\n    # File exists but hash doesn't match known bad \u2014 still suspicious\n    if [[ $INFECTED -eq 0 ]]; then\n      warn \"Suspicious payload filename found (hash doesn't match known bad, but verify): $fpath\"\n      warn \"  SHA-256: $actual_hash\"\n      flag_suspicious \"unexpected payload-named file: $fpath\"\n    fi\n  done &lt;&lt;&lt; \"$PAYLOAD_FILES\"\nfi\n\n# \u2500\u2500 Step 5: gh-token-monitor daemon \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"5/7  Persistence daemon (gh-token-monitor)\"\n\nDAEMON_FOUND=0\n\n# macOS LaunchAgents\nif [[ -d ~/Library/LaunchAgents ]]; then\n  DAEMON_PLIST=$(ls ~/Library/LaunchAgents/ 2&gt;/dev/null | grep -i \"token\" || true)\n  if [[ -n \"$DAEMON_PLIST\" ]]; then\n    hit \"Suspicious LaunchAgent found: $DAEMON_PLIST\"\n    hit \"  Full path: ~/Library/LaunchAgents/$DAEMON_PLIST\"\n    flag_infected \"gh-token-monitor LaunchAgent present \u2014 DO NOT rotate tokens yet\"\n    DAEMON_FOUND=1\n  fi\n\n  if launchctl list 2&gt;/dev/null | grep -qi \"gh-token\"; then\n    hit \"gh-token-monitor service is RUNNING (launchctl)\"\n    flag_infected \"gh-token-monitor daemon is active\"\n    DAEMON_FOUND=1\n  fi\nfi\n\n# Linux systemd user units\nif command -v systemctl &amp;&gt;/dev/null; then\n  if systemctl --user list-units 2&gt;/dev/null | grep -qi \"gh-token\"; then\n    hit \"gh-token-monitor systemd user unit found\"\n    flag_infected \"gh-token-monitor daemon present (systemd)\"\n    DAEMON_FOUND=1\n  fi\nfi\n\n# Broader token-monitor process check\nif pgrep -f \"gh-token-monitor\" &amp;&gt;/dev/null; then\n  hit \"gh-token-monitor process is running right now\"\n  flag_infected \"gh-token-monitor process active\"\n  DAEMON_FOUND=1\nfi\n\nif [[ $DAEMON_FOUND -eq 0 ]]; then\n  ok \"No gh-token-monitor daemon detected (LaunchAgents, systemd, pgrep)\"\nfi\n\n# \u2500\u2500 Step 6: Claude Code hooks injection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"6/7  Claude Code hooks (~/.claude/settings.json)\"\n\nCLAUDE_SETTINGS=~/.claude/settings.json\nif [[ -f \"$CLAUDE_SETTINGS\" ]]; then\n  # Look for PreToolUse / PostToolUse hooks the user didn't add\n  if grep -qE \"(PreToolUse|PostToolUse)\" \"$CLAUDE_SETTINGS\" 2&gt;/dev/null; then\n    warn \"Claude Code settings.json contains hook entries \u2014 verify these are yours:\"\n    grep -E \"(PreToolUse|PostToolUse|hooks)\" \"$CLAUDE_SETTINGS\" 2&gt;/dev/null | head -20 | while IFS= read -r line; do\n      warn \"  $line\"\n    done\n    flag_suspicious \"unexpected hook entries in ~/.claude/settings.json \u2014 manual review required\"\n  else\n    ok \"No unexpected hooks in ~/.claude/settings.json\"\n  fi\nelse\n  info \"~/.claude/settings.json not found (Claude Code not installed, or no settings)\"\nfi\n\n# \u2500\u2500 Step 7: GitHub Actions / Dune-themed branches \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nbanner \"7/7  GitHub Actions (local .github/workflows check)\"\n\nWORKFLOWS_DIR=\"$PROJECT_DIR/.github/workflows\"\nif [[ -d \"$WORKFLOWS_DIR\" ]]; then\n  # Check for pull_request_target triggers (the attack vector)\n  PRT_HIT=$(grep -rl \"pull_request_target\" \"$WORKFLOWS_DIR\" 2&gt;/dev/null || true)\n  if [[ -n \"$PRT_HIT\" ]]; then\n    warn \"Workflow(s) with pull_request_target trigger found \u2014 this was the CI attack vector:\"\n    while IFS= read -r wf; do\n      warn \"  $wf\"\n    done &lt;&lt;&lt; \"$PRT_HIT\"\n    flag_suspicious \"pull_request_target in workflows \u2014 verify intentional use\"\n  else\n    ok \"No pull_request_target triggers in .github/workflows\"\n  fi\n\n  # Check for unpinned uses: (tags instead of SHA)\n  UNPINNED=$(grep -rh \"uses:\" \"$WORKFLOWS_DIR\" 2&gt;/dev/null | grep -v \"@[0-9a-f]\\{40\\}\" | grep \"@\" || true)\n  if [[ -n \"$UNPINNED\" ]]; then\n    warn \"Workflow action(s) not pinned to full commit SHA (supply-chain risk):\"\n    echo \"$UNPINNED\" | head -10 | while IFS= read -r line; do\n      warn \"  $line\"\n    done\n    flag_suspicious \"unpinned workflow actions \u2014 pin to SHA not tag for defense-in-depth\"\n  else\n    ok \"All detected workflow actions appear pinned to SHA\"\n  fi\nelse\n  info \"No .github/workflows directory found \u2014 skipping Actions check\"\nfi\n\n# \u2500\u2500 Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nprintf \"\\n\"\nsep\nprintf \"${BOLD}  SCAN COMPLETE${RESET}\\n\"\nsep\n\nif [[ ${#FINDINGS[@]} -gt 0 ]]; then\n  printf \"\\n${BOLD}  Findings:${RESET}\\n\"\n  for f in \"${FINDINGS[@]}\"; do\n    printf \"    \u2022 %s\\n\" \"$f\"\n  done\nfi\n\nprintf \"\\n\"\n\nif [[ $INFECTED -eq 1 ]]; then\n  printf \"${RED}${BOLD}\"\n  sep\n  printf \"  VERDICT: INFECTED\\n\"\n  sep\n  printf \"${RESET}\"\n  printf \"\\n\"\n  printf \"${RED}${BOLD}  !! CRITICAL \u2014 READ BEFORE DOING ANYTHING !!${RESET}\\n\\n\"\n  printf \"  The worm's daemon (gh-token-monitor) runs 'rm -rf ~' if it detects\\n\"\n  printf \"  token revocation. Follow this order EXACTLY:\\n\\n\"\n  printf \"    1. DISCONNECT NETWORK NOW (Wi-Fi off, ethernet unplugged)\\n\"\n  printf \"    2. Do NOT run npm install / npm uninstall / git push\\n\"\n  printf \"    3. Remove the daemon FIRST:\\n\"\n  printf \"         macOS: launchctl unload ~/Library/LaunchAgents/.plist\\n\"\n  printf \"                rm ~/Library/LaunchAgents/.plist\\n\"\n  printf \"         Linux: systemctl --user stop gh-token-monitor.service\\n\"\n  printf \"                rm ~/.config/systemd/user/gh-token-monitor.service\\n\"\n  printf \"    4. Remove payload files:\\n\"\n  printf \"         find ~ \\\\( -name router_init.js -o -name tanstack_runner.js \\\\) -delete\\n\"\n  printf \"    5. ONLY THEN reconnect and rotate tokens (GitHub PAT first)\\n\"\n  printf \"    6. Reinstall dependencies: rm -rf node_modules &amp;&amp; npm install --ignore-scripts\\n\\n\"\n  printf \"  Full remediation runbook:\\n\"\n  printf \"  https://github.com/prashanthnatraj/shai-hulud-detector#remediation\\n\\n\"\n  exit 2\nelif [[ $SUSPICIOUS -eq 1 ]]; then\n  printf \"${YELLOW}${BOLD}\"\n  sep\n  printf \"  VERDICT: SUSPICIOUS \u2014 manual review required\\n\"\n  sep\n  printf \"${RESET}\"\n  printf \"\\n\"\n  printf \"  Some checks returned warnings. This does not confirm infection,\\n\"\n  printf \"  but you should investigate each finding above before proceeding.\\n\\n\"\n  printf \"  Community scanners for deeper analysis (run before rotating tokens):\\n\"\n  printf \"    \u2022 https://github.com/Cobenian/shai-hulud-detect\\n\"\n  printf \"    \u2022 https://github.com/omarpr/mini-shai-hulud-ioc-scanner\\n\"\n  printf \"    \u2022 StepSecurity scanner (most comprehensive)\\n\\n\"\n  printf \"  Full runbook: https://github.com/prashanthnatraj/shai-hulud-detector#remediation\\n\\n\"\n  exit 1\nelse\n  printf \"${GREEN}${BOLD}\"\n  sep\n  printf \"  VERDICT: CLEAN\\n\"\n  sep\n  printf \"${RESET}\"\n  printf \"\\n\"\n  printf \"  No indicators of Mini Shai-Hulud (CVE-2026-45321) found.\\n\\n\"\n  printf \"  Hardening recommendations (apply regardless of scan result):\\n\"\n  printf \"    \u2022 Add to .npmrc:  ignore-scripts=true\\n\"\n  printf \"    \u2022 Add to .npmrc:  minimum-release-age=4320   (3-day buffer)\\n\"\n  printf \"    \u2022 Pin GitHub Actions to full commit SHA, not version tags\\n\"\n  printf \"    \u2022 Enable npm 2FA: npm profile enable-2fa auth-and-writes\\n\"\n  printf \"    \u2022 Sign up for a supply-chain scanner: socket.dev, aikido.dev, or snyk.io\\n\\n\"\n  exit 0\nfi\n", "creation_timestamp": "2026-05-19T05:42:43.000000Z"}, {"uuid": "4302cc20-19d5-40a0-b107-74903ac94cd9", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "published-proof-of-concept", "source": "Telegram/8zfghiqhdMgUnQpN-sW_sONu8d5R6D_u0VHsC67HR3Je1Bs", "content": "", "creation_timestamp": "2026-05-16T15:00:07.000000Z"}, {"uuid": "d1468a0b-3c9f-4c39-a594-51b349525778", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://gist.github.com/Fazzani/f2627d3f340f16643f41c7cab7a6b772", "content": "# ============================================================\n# Invoke-MiniShaiHuludCheck.ps1\n# CTI Advisory #002 \u2014 CVE-2026-45321 \u2014 TLP:AMBER\n# Threat actor: TeamPCP (DeadCatx3 / PCPcat / ShellForce / CipherForce)\n# CVSS 9.6 Critical\n#\n# Validates whether REAL artefacts are present on disk \u2014\n# not just traces in PowerShell history.\n#\n# Designed to run via one-liner (Gist):\n#   Windows (PowerShell):  irm  | iex\n#   macOS / Linux:         curl -fsSL  | pwsh\n#\n# \u26a0  CRITICAL SAFETY ORDER:\n#    ISOLATE machine \u2192 IMAGE \u2192 KILL DAEMON \u2192 REVOKE secrets \u2192 ROTATE\n#    DO NOT revoke tokens before network isolation.\n#    The worm watchdog triggers data wipe on token revocation.\n# ============================================================\n\nSet-StrictMode -Version Latest\n\n# \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction Write-Header {\n    param([string]$Text)\n    Write-Host \"`n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\" -ForegroundColor Cyan\n    Write-Host \" $Text\" -ForegroundColor Cyan\n    Write-Host \"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\" -ForegroundColor Cyan\n}\n\nfunction Write-Section {\n    param([string]$Text)\n    Write-Host \"`n\u2500\u2500 $Text \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\" -ForegroundColor DarkCyan\n}\n\nfunction Write-Hit {\n    param([string]$Severity, [string]$Label, [string]$Detail)\n    $color = switch ($Severity) {\n        'critical' { 'Red' }\n        'high'     { 'Yellow' }\n        default    { 'Magenta' }\n    }\n    Write-Host \"  [$(($Severity).ToUpper())] $Label\" -ForegroundColor $color\n    if ($Detail) {\n        Write-Host \"         \u2192 $Detail\" -ForegroundColor DarkGray\n    }\n}\n\nfunction Write-OK {\n    param([string]$Text)\n    Write-Host \"  [OK]  $Text\" -ForegroundColor Green\n}\n\n# \u2500\u2500 IOC Definitions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n$PAYLOAD_FILES = @(\n    @{ Name = 'setup_bun.js';         Severity = 'critical'; Hash = $null }\n    @{ Name = 'bun_environment.js';   Severity = 'critical'; Hash = $null }\n    @{ Name = 'router_init.js';       Severity = 'critical'; Hash = 'ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c' }\n    @{ Name = 'router_runtime.js';    Severity = 'critical'; Hash = $null }\n    @{ Name = 'tanstack_runner.js';   Severity = 'critical'; Hash = '2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96' }\n)\n\n$PERSISTENCE_PATHS = @(\n    @{ RelPath = '.claude\\settings.json'; Severity = 'critical'; Label = '.claude/settings.json (modified by worm)' }\n    @{ RelPath = '.claude\\setup.mjs';     Severity = 'critical'; Label = '.claude/setup.mjs (worm dropper)' }\n    @{ RelPath = '.claude.json';          Severity = 'critical'; Label = '.claude.json (harvested by worm)' }\n    @{ RelPath = '.vscode\\tasks.json';    Severity = 'critical'; Label = '.vscode/tasks.json (modified by worm)' }\n    @{ RelPath = '.vscode\\setup.mjs';     Severity = 'critical'; Label = '.vscode/setup.mjs (worm dropper)' }\n)\n\n$SUSPICIOUS_PROCESSES = @(\n    @{ Pattern = 'gh-token-monitor'; Severity = 'critical'; Label = 'gh-token-monitor daemon (persistence)' }\n    @{ Pattern = 'tanstack';         Severity = 'critical'; Label = 'tanstack_runner process' }\n    @{ Pattern = 'router_runtime';   Severity = 'critical'; Label = 'router_runtime process' }\n)\n\n$C2_DOMAINS = @(\n    'git-tanstack.com'\n    'seed1.getsession.org'\n    'zero.masscan.cloud'\n)\n\n$SUSPICIOUS_STRINGS = @(\n    @{ Pattern = 'A Mini Shai-Hulud has Appeared';              Severity = 'critical' }\n    @{ Pattern = 'Sha1-Hulud: The Second Coming';               Severity = 'critical' }\n    @{ Pattern = 'Shai-Hulud: Here We Go Again';                Severity = 'critical' }\n    @{ Pattern = 'IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner'; Severity = 'critical' }\n    @{ Pattern = 'ctf-scramble-v2';                             Severity = 'critical' }\n    @{ Pattern = 'OhNoWhatsGoingOnWithGitHub:';                 Severity = 'critical' }\n    @{ Pattern = 'svksjrhjkcejg';                               Severity = 'critical' }\n)\n\n$COMPROMISED_PACKAGES = @(\n    '@mistralai/mistralai'\n    '@uipath/apollo-core'\n    'intercom-client@7.0.4'\n    'mbt@1.2.48'\n    '@cap-js/db-service'\n    '@cap-js/sqlite@2.2.2'\n    '@cap-js/postgres'\n)\n\n# \u2500\u2500 State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n$findings = [System.Collections.Generic.List[hashtable]]::new()\n\nfunction Add-Finding {\n    param([string]$Severity, [string]$Category, [string]$Label, [string]$Detail = '')\n    $findings.Add(@{ Severity = $Severity; Category = $Category; Label = $Label; Detail = $Detail })\n    Write-Hit -Severity $Severity -Label $Label -Detail $Detail\n}\n\n# \u2500\u2500 Banner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nWrite-Host @\"\n`n\n  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n  \u2551   Mini Shai-Hulud \u2014 Artefact Verification                   \u2551\n  \u2551   CTI Advisory #002 \u00b7 CVE-2026-45321 \u00b7 TLP:AMBER            \u2551\n  \u2551                                                              \u2551\n  \u2551   \u26a0  ISOLATE machine BEFORE revoking any token/secret  \u26a0   \u2551\n  \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n\"@ -ForegroundColor DarkYellow\n\n$auditDate = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')\n$hostname  = $env:COMPUTERNAME\nWrite-Host \"  Host   : $hostname\" -ForegroundColor Gray\nWrite-Host \"  Date   : $auditDate\" -ForegroundColor Gray\nWrite-Host \"  User   : $($env:USERNAME)`n\" -ForegroundColor Gray\n\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n# 1. PAYLOAD FILES ON DISK\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nWrite-Header \"1/5 \u2014 Payload files on disk\"\n\n$searchRoots = @(\n    $env:USERPROFILE\n    $env:APPDATA\n    $env:LOCALAPPDATA\n    $env:TEMP\n    [System.IO.Path]::GetTempPath()\n    'C:\\ProgramData'\n) | Where-Object { $_ -and (Test-Path $_) } | Sort-Object -Unique\n\nforeach ($payload in $PAYLOAD_FILES) {\n    $found = $false\n    foreach ($root in $searchRoots) {\n        try {\n            $matches_ = Get-ChildItem -Path $root -Recurse -Filter $payload.Name `\n                -ErrorAction SilentlyContinue -Force 2&gt;$null\n            foreach ($f in $matches_) {\n                $found = $true\n                $detail = $f.FullName\n\n                # SHA-256 verification\n                if ($payload.Hash) {\n                    try {\n                        $actual = (Get-FileHash $f.FullName -Algorithm SHA256).Hash.ToLower()\n                        if ($actual -eq $payload.Hash) {\n                            $detail = \"$($f.FullName) \u2014 HASH CONFIRMED malicious ($actual)\"\n                        } else {\n                            $detail = \"$($f.FullName) \u2014 hash mismatch (actual: $actual)\"\n                            # File exists but hash differs \u2014 still suspicious, treat as high\n                        }\n                    } catch { $detail = \"$($f.FullName) \u2014 could not hash file\" }\n                }\n\n                Add-Finding -Severity $payload.Severity `\n                            -Category 'filesystem' `\n                            -Label    \"$($payload.Name) found on disk\" `\n                            -Detail   $detail\n            }\n        } catch { &lt;# silently skip inaccessible dirs #&gt; }\n    }\n    if (-not $found) {\n        Write-OK \"$($payload.Name) \u2014 not found on disk\"\n    }\n}\n\n# Special case: Linux payload (running via WSL or cross-platform)\nif (Test-Path '/tmp/transformers.pyz' -ErrorAction SilentlyContinue) {\n    Add-Finding -Severity 'critical' -Category 'filesystem' `\n                -Label '/tmp/transformers.pyz (PyPI mistralai payload)' `\n                -Detail '/tmp/transformers.pyz'\n} else {\n    Write-OK \"/tmp/transformers.pyz \u2014 not found\"\n}\n\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n# 2. PERSISTENCE ARTEFACTS\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nWrite-Header \"2/5 \u2014 Persistence artefacts\"\n\nforeach ($p in $PERSISTENCE_PATHS) {\n    $fullPath = Join-Path $env:USERPROFILE $p.RelPath\n    if (Test-Path $fullPath -ErrorAction SilentlyContinue) {\n        Add-Finding -Severity $p.Severity -Category 'persistence' `\n                    -Label $p.Label -Detail $fullPath\n    } else {\n        Write-OK \"$($p.Label) \u2014 not found\"\n    }\n}\n\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n# 3. PROCESSES\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nWrite-Header \"3/5 \u2014 Running processes\"\n\nforeach ($proc in $SUSPICIOUS_PROCESSES) {\n    $running = Get-Process -ErrorAction SilentlyContinue | Where-Object {\n        $_.ProcessName -match $proc.Pattern -or\n        ($_.MainModule -and $_.MainModule.FileName -match $proc.Pattern)\n    }\n    if ($running) {\n        foreach ($r in $running) {\n            Add-Finding -Severity $proc.Severity -Category 'process' `\n                        -Label $proc.Label `\n                        -Detail \"PID=$($r.Id)  Name=$($r.ProcessName)\"\n        }\n    } else {\n        Write-OK \"$($proc.Label) \u2014 not running\"\n    }\n}\n\n# gh-token-monitor via scheduled task\nWrite-Section \"Scheduled tasks\"\ntry {\n    $tasks = Get-ScheduledTask -ErrorAction SilentlyContinue |\n             Where-Object { $_.TaskName -match 'bun|tanstack|router|gh-token|monitor' }\n    if ($tasks) {\n        foreach ($t in $tasks) {\n            Add-Finding -Severity 'critical' -Category 'persistence' `\n                        -Label \"Suspicious scheduled task: $($t.TaskName)\" `\n                        -Detail $t.TaskPath\n        }\n    } else {\n        Write-OK \"No suspicious scheduled tasks found\"\n    }\n} catch {\n    Write-Host \"  [INFO] Could not query scheduled tasks: $_\" -ForegroundColor DarkGray\n}\n\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n# 4. NETWORK \u2014 Active connections to C2 domains\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nWrite-Header \"4/5 \u2014 Active network connections to C2\"\n\n$tcpConnections = Get-NetTCPConnection -ErrorAction SilentlyContinue\nforeach ($domain in $C2_DOMAINS) {\n    try {\n        $resolved = [System.Net.Dns]::GetHostAddresses($domain) | Select-Object -ExpandProperty IPAddressToString\n    } catch { $resolved = @() }\n\n    $hit = $tcpConnections | Where-Object {\n        $resolved -contains $_.RemoteAddress\n    }\n    if ($hit) {\n        foreach ($c in $hit) {\n            Add-Finding -Severity 'critical' -Category 'network' `\n                        -Label \"Active connection to C2: $domain\" `\n                        -Detail \"Local=$($c.LocalAddress):$($c.LocalPort)  Remote=$($c.RemoteAddress):$($c.RemotePort)  PID=$($c.OwningProcess)\"\n        }\n    } else {\n        Write-OK \"$domain \u2014 no active connection\"\n    }\n}\n\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n# 5. SUSPICIOUS STRINGS in .json / .js / .mjs / .lock / .txt files\n#    (limited to user profile \u2014 not full disk scan)\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nWrite-Header \"5/5 \u2014 Suspicious strings in config / source files\"\n\n$stringScanExtensions = @('*.json', '*.js', '*.mjs', '*.lock', '*.txt', '*.yml', '*.yaml')\n$stringScanRoots = @(\n    $env:USERPROFILE\n    $env:APPDATA\n    $env:LOCALAPPDATA\n) | Where-Object { $_ -and (Test-Path $_) } | Sort-Object -Unique\n\n# Build one combined regex for efficiency\n$combinedPattern = ($SUSPICIOUS_STRINGS | ForEach-Object { [Regex]::Escape($_.Pattern) }) -join '|'\n$combinedRegex   = [System.Text.RegularExpressions.Regex]::new($combinedPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)\n\n# Track found strings to avoid duplicate reporting per string\n$foundStrings = @{}\n\nforeach ($root in $stringScanRoots) {\n    foreach ($ext in $stringScanExtensions) {\n        try {\n            $files = Get-ChildItem -Path $root -Recurse -Filter $ext `\n                -ErrorAction SilentlyContinue -Force 2&gt;$null |\n                Where-Object { $_.Length -lt 5MB }  # skip huge files\n            foreach ($f in $files) {\n                try {\n                    $content = [System.IO.File]::ReadAllText($f.FullName, [System.Text.Encoding]::UTF8)\n                    $m = $combinedRegex.Match($content)\n                    if ($m.Success) {\n                        $key = \"$($f.FullName)|$($m.Value)\"\n                        if (-not $foundStrings.ContainsKey($key)) {\n                            $foundStrings[$key] = $true\n                            $ioc = $SUSPICIOUS_STRINGS | Where-Object {\n                                $content -imatch [Regex]::Escape($_.Pattern)\n                            } | Select-Object -First 1\n                            Add-Finding -Severity ($ioc ? $ioc.Severity : 'critical') `\n                                        -Category 'string' `\n                                        -Label \"Campaign marker found: `\"$($m.Value)`\"\" `\n                                        -Detail $f.FullName\n                        }\n                    }\n                } catch { &lt;# skip unreadable files #&gt; }\n            }\n        } catch { &lt;# skip inaccessible dirs #&gt; }\n    }\n}\n\n# Check compromised packages in package.json / package-lock.json\nWrite-Section \"Compromised packages in package.json\"\n$pkgFiles = Get-ChildItem -Path $env:USERPROFILE -Recurse -ErrorAction SilentlyContinue `\n    -Include 'package.json','package-lock.json' -Force 2&gt;$null |\n    Where-Object { $_.Length -lt 2MB -and $_.FullName -notmatch '\\\\node_modules\\\\' }\n\n$pkgHits = @{}\nforeach ($f in $pkgFiles) {\n    try {\n        $raw = [System.IO.File]::ReadAllText($f.FullName)\n        foreach ($pkg in $COMPROMISED_PACKAGES) {\n            if ($raw -imatch [Regex]::Escape($pkg)) {\n                $key = \"$($f.FullName)|$pkg\"\n                if (-not $pkgHits.ContainsKey($key)) {\n                    $pkgHits[$key] = $true\n                    Add-Finding -Severity 'critical' -Category 'package' `\n                                -Label \"Compromised package referenced: $pkg\" `\n                                -Detail $f.FullName\n                }\n            }\n        }\n    } catch { &lt;# skip #&gt; }\n}\n\nif ($pkgHits.Count -eq 0) {\n    Write-OK \"No compromised packages found in package.json files\"\n}\n\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n# SUMMARY\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nWrite-Header \"SUMMARY \u2014 $hostname \u2014 $auditDate\"\n\n$criticalCount = ($findings | Where-Object { $_.Severity -eq 'critical' }).Count\n$highCount     = ($findings | Where-Object { $_.Severity -eq 'high' }).Count\n$total         = $findings.Count\n\nif ($total -eq 0) {\n    Write-Host \"`n  \u2714  NO ARTEFACTS FOUND \u2014 Likely false positive.\" -ForegroundColor Green\n    Write-Host \"     The previous report matched only command-history traces, not real files.\" -ForegroundColor Gray\n    Write-Host \"     Recommended: keep monitoring, no immediate escalation required.`n\" -ForegroundColor Gray\n} else {\n    Write-Host \"`n  \u2718  $total finding(s): $criticalCount CRITICAL, $highCount HIGH\" -ForegroundColor Red\n    Write-Host \"\"\n\n    if ($criticalCount -gt 0) {\n        Write-Host \"  MANDATORY INCIDENT RESPONSE ORDER:\" -ForegroundColor Red\n        Write-Host \"   1. ISOLATE the machine from the network NOW (do not revoke tokens yet)\" -ForegroundColor Yellow\n        Write-Host \"   2. PRESERVE evidence \u2014 do not reboot, do not delete files\" -ForegroundColor Yellow\n        Write-Host \"   3. IMAGE the disk if possible\" -ForegroundColor Yellow\n        Write-Host \"   4. KILL the gh-token-monitor daemon (if running)\" -ForegroundColor Yellow\n        Write-Host \"   5. THEN revoke and rotate all exposed secrets\" -ForegroundColor Yellow\n        Write-Host \"   6. ESCALATE to CSIRT with this output and the CSV report\" -ForegroundColor Yellow\n    }\n\n    Write-Host \"`n  Findings detail:\" -ForegroundColor DarkYellow\n    foreach ($f in $findings) {\n        $color = if ($f.Severity -eq 'critical') { 'Red' } elseif ($f.Severity -eq 'high') { 'Yellow' } else { 'Magenta' }\n        Write-Host \"    [$($f.Severity.ToUpper())][$($f.Category)] $($f.Label)\" -ForegroundColor $color\n        if ($f.Detail) {\n            Write-Host \"       $($f.Detail)\" -ForegroundColor DarkGray\n        }\n    }\n    Write-Host \"\"\n}\n", "creation_timestamp": "2026-05-19T12:35:06.000000Z"}, {"uuid": "d77995ad-2011-4a29-93c9-9a64f7e92d0f", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://t.me/GithubRedTeam/84445", "content": "\ud83d\udea8 GitHub \u76d1\u63a7\u6d88\u606f\u63d0\u9192\n\n\ud83d\udea8 \u53d1\u73b0\u5173\u952e\u8bcd\uff1a #CVE-2026\n\n\ud83d\udce6 \u9879\u76ee\u540d\u79f0\uff1a tanstack-compromise-checker\n\ud83d\udc64 \u9879\u76ee\u4f5c\u8005\uff1a fabriziosalmi\n\ud83d\udee0 \u5f00\u53d1\u8bed\u8a00\uff1a Shell\n\u2b50 Star\u6570\u91cf\uff1a 0  |  \ud83c\udf74 Fork\u6570\u91cf\uff1a 0\n\ud83d\udcc5 \u66f4\u65b0\u65f6\u95f4\uff1a 2026-05-16 11:51:00\n\n\ud83d\udcdd \u9879\u76ee\u63cf\u8ff0\uff1a\nShell script to detect TanStack npm supply chain attack indicators (CVE-2026-45321 / GHSA-g7cv-rxg3-hmpx)\n\n\ud83d\udd17 \u70b9\u51fb\u8bbf\u95ee\u9879\u76ee\u5730\u5740", "creation_timestamp": "2026-05-16T12:00:04.000000Z"}, {"uuid": "356bde62-c939-46ed-a500-d35b2fa605eb", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://t.me/GithubRedTeam/84450", "content": "\ud83d\udea8 GitHub \u76d1\u63a7\u6d88\u606f\u63d0\u9192\n\n\ud83d\udea8 \u53d1\u73b0\u5173\u952e\u8bcd\uff1a #CVE-2026\n\n\ud83d\udce6 \u9879\u76ee\u540d\u79f0\uff1a shai-scan\n\ud83d\udc64 \u9879\u76ee\u4f5c\u8005\uff1a digi4care\n\ud83d\udee0 \u5f00\u53d1\u8bed\u8a00\uff1a TypeScript\n\u2b50 Star\u6570\u91cf\uff1a 0  |  \ud83c\udf74 Fork\u6570\u91cf\uff1a 0\n\ud83d\udcc5 \u66f4\u65b0\u65f6\u95f4\uff1a 2026-05-16 12:58:05\n\n\ud83d\udcdd \u9879\u76ee\u63cf\u8ff0\uff1a\nZero-dependency CLI scanner for npm/PyPI supply chain compromises. Detects compromised packages in lockfiles and system-level IOCs from attacks like Mini Shai-Hulud (CVE-2026-45321).\n\n\ud83d\udd17 \u70b9\u51fb\u8bbf\u95ee\u9879\u76ee\u5730\u5740", "creation_timestamp": "2026-05-16T13:00:04.000000Z"}, {"uuid": "6397bdc2-5791-4276-8c4f-1b9d52b87c4c", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "https://gist.github.com/Fazzani/7a76d5ae430e05c763c6a86cbca9d42f", "content": "#!/usr/bin/env node\n'use strict';\n\n/**\n * mini-shai-hulud-audit.js\n * Workstation / runner scanner for the Mini Shai-Hulud campaign (CTI Advisory #002)\n * CVE-2026-45321 / GHSA-g7cv-rxg3-hmpx \u2014 CVSS 9.6 Critical\n * Threat actor: TeamPCP (aliases: DeadCatx3, PCPcat, ShellForce, CipherForce)\n *\n * \u26a0\ufe0f  CRITICAL SAFETY WARNING  \u26a0\ufe0f\n * If infection is suspected, DO NOT revoke any tokens before isolating the machine.\n * The malware watchdog detects revocation and triggers: rm -rf ~/\n * Incident response order: ISOLATE \u2192 IMAGE \u2192 KILL DAEMON \u2192 REVOKE \u2192 ROTATE\n *\n * Zero runtime dependencies \u2014 requires Node.js &gt;= 14 only.\n * Usage: node mini-shai-hulud/mini-shai-hulud-audit.js [--root ] [--output ]\n */\n\nconst fs = require('node:fs');\nconst os = require('node:os');\nconst path = require('node:path');\nconst { execSync } = require('node:child_process');\n\nconst { MINI_SHAI_HULUD_PATTERNS } = require('./incident-patterns-mini-shai-hulud');\nconst { createLogger, dedupeByKey, makeProgress, readTextIfExists, scanTextForPatterns, walkFiles, writeReportFile } = require('../incident-utils');\n\nconst REPORT_FILE = 'mini-shai-hulud-audit-report.csv';\nconst ADVISORY_REF = 'CTI Advisory #002 \u2014 CVE-2026-45321 \u2014 TLP:AMBER';\n\nconst CSV_COLUMNS = [\n  'recordType',\n  'auditDate',\n  'host',\n  'platform',\n  'root',\n  'checkType',\n  'targetedFiles',\n  'scannedFiles',\n  'filesWithHits',\n  'totalHits',\n  'criticalHits',\n  'categories',\n  'path',\n  'size',\n  'mtime',\n  'id',\n  'label',\n  'category',\n  'severity',\n  'match',\n  'snippet',\n];\n\n// \u2500\u2500 CLI argument parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction parseArgs(argv) {\n  const args = argv.slice(2);\n  const opts = {\n    output: REPORT_FILE,\n    root: null,\n    help: false,\n    maxDepth: 4,\n    lockfiles: false, // opt-in: scan lockfiles for IOCs (slow on large repos)\n  };\n\n  for (let i = 0; i &lt; args.length; i++) {\n    switch (args[i]) {\n      case '--output':\n      case '-o':\n        opts.output = args[++i];\n        break;\n      case '--root':\n      case '-r':\n        opts.root = args[++i];\n        break;\n      case '--max-depth':\n      case '--depth':\n        opts.maxDepth = Number(args[++i]);\n        break;\n      case '--lockfiles':\n        opts.lockfiles = true;\n        break;\n      case '--help':\n      case '-h':\n        opts.help = true;\n        break;\n    }\n  }\n\n  return opts;\n}\n\n// \u2500\u2500 Help \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction showHelp() {\n  const { C } = createLogger();\n  console.log(`\n${C.bold}${C.red}\u26a0\ufe0f  CRITICAL SAFETY WARNING${C.reset}\n  Do NOT revoke any tokens before isolating this machine.\n  The worm's watchdog triggers ${C.bold}rm -rf ~/${C.reset} on token revocation.\n\n${C.bold}${C.cyan}mini-shai-hulud-audit${C.reset}\nLocal evidence collector for the Mini Shai-Hulud campaign\n${C.dim}${ADVISORY_REF}${C.reset}\n\nUSAGE\n  node mini-shai-hulud/mini-shai-hulud-audit.js [options]\n\nOPTIONS\n  --root, -r      Extra folder to scan recursively for text evidence\n  --output, -o    Output file (default: ${REPORT_FILE})\n  --max-depth        Max recursion depth for --root (default: 4)\n  --lockfiles           Also scan lockfiles (package-lock.json, yarn.lock...)\n                        for IOC references \u2014 slow but thorough\n  --help, -h            Show this help\n\nDEFAULT SCAN TARGETS\n  Payload files   setup_bun.js, bun_environment.js, router_init.js,\n                  router_runtime.js, tanstack_runner.js (home + temp dirs)\n  Linux           /tmp/transformers.pyz (PyPI mistralai payload)\n  Persistence     ~/.claude/settings.json, .vscode/tasks.json, ~/.claude.json,\n                  */.claude/setup.mjs, */.vscode/setup.mjs\n  Daemons         ~/.config/**/gh-token-monitor*, ~/.local/bin/gh-token-monitor.sh\n  Shell history   bash_history, zsh_history, PSReadLine\n  npm logs        ~/.npm/_logs, AppData npm-cache logs\n\nINCIDENT RESPONSE ORDER (if infection confirmed)\n  1. ISOLATE  \u2014 disconnect machine from network\n  2. IMAGE    \u2014 forensic disk image before any cleanup\n  3. KILL     \u2014 disable gh-token-monitor daemon\n  4. REVOKE   \u2014 npm, GitHub PAT/OAuth, AWS, Azure, GCP, Kubernetes, SSH\n  5. ROTATE   \u2014 all credentials reachable from this host\n  6. AUDIT    \u2014 CloudTrail / Azure Activity / GCP Audit logs\n\nREFERENCES\n  CVE-2026-45321        https://tenable.com/cve/CVE-2026-45321\n  GHSA-g7cv-rxg3-hmpx   https://github.com/TanStack/router/security/advisories/GHSA-g7cv-rxg3-hmpx\n  TanStack postmortem   https://tanstack.com/blog/npm-supply-chain-compromise-postmortem\n`);\n}\n\n// \u2500\u2500 IOC-specific artefact paths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction joinIfPresent(...parts) {\n  if (parts.some((part) =&gt; !part)) return null;\n  return path.join(...parts);\n}\n\nfunction getDefaultTargets() {\n  const home = os.homedir();\n  const tempDir = process.env.TEMP || process.env.TMP || os.tmpdir();\n  const appData = process.env.APPDATA;\n  const localAppData = process.env.LOCALAPPDATA;\n\n  // \u2500\u2500 Payload files to check for existence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  // These files should not exist on a clean machine \u2014 their presence is high-confidence.\n  const payloadFileNames = ['setup_bun.js', 'bun_environment.js', 'router_init.js', 'router_runtime.js', 'tanstack_runner.js'];\n\n  const payloadSearchRoots = [home, tempDir, os.tmpdir()].filter(Boolean);\n  const knownPayloadPaths = [];\n  for (const root of payloadSearchRoots) {\n    for (const name of payloadFileNames) {\n      knownPayloadPaths.push(path.join(root, name));\n    }\n  }\n\n  // Linux-specific PyPI payload\n  knownPayloadPaths.push('/tmp/transformers.pyz');\n\n  // \u2500\u2500 Persistence configs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const persistenceFiles = [\n    joinIfPresent(home, '.claude', 'settings.json'),\n    joinIfPresent(home, '.vscode', 'tasks.json'),\n    joinIfPresent(home, '.claude.json'),\n    joinIfPresent(home, '.claude', 'setup.mjs'),\n    joinIfPresent(home, '.vscode', 'setup.mjs'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 Daemon locations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const daemonFiles = [\n    joinIfPresent(home, '.local', 'bin', 'gh-token-monitor.sh'),\n    joinIfPresent(home, '.config', 'systemd', 'user', 'gh-token-monitor.service'),\n    joinIfPresent(home, 'Library', 'LaunchAgents', 'com.gh-token-monitor.plist'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 Shell history \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const historyFiles = [\n    joinIfPresent(home, '.bash_history'),\n    joinIfPresent(home, '.zsh_history'),\n    joinIfPresent(home, '.local', 'share', 'fish', 'fish_history'),\n    joinIfPresent(appData, 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt'),\n    joinIfPresent(appData, 'Microsoft', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt'),\n    joinIfPresent(home, 'AppData', 'Roaming', 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 npm debug logs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const logDirs = [\n    joinIfPresent(home, '.npm', '_logs'),\n    joinIfPresent(appData, 'npm-cache', '_logs'),\n    joinIfPresent(localAppData, 'npm-cache', '_logs'),\n    joinIfPresent(home, 'Library', 'Logs', 'npm'),\n  ].filter(Boolean);\n\n  // \u2500\u2500 Daemon config dirs to scan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const daemonSearchDirs = [joinIfPresent(home, '.config'), joinIfPresent(home, '.local', 'bin'), joinIfPresent(home, 'Library', 'LaunchAgents')].filter(Boolean);\n\n  return {\n    files: [...knownPayloadPaths, ...persistenceFiles, ...daemonFiles, ...historyFiles],\n    dirs: [...logDirs, ...daemonSearchDirs],\n  };\n}\n\n// \u2500\u2500 File filter for directory walks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction isTextLikeEvidence(filePath, opts) {\n  const name = path.basename(filePath).toLowerCase();\n\n  if (!opts.lockfiles) {\n    // Lock files have high noise for most patterns \u2014 skip unless opted in.\n    // Exception: we DO want to check package-lock.json for the poisoned commit ref\n    // and optionalDependencies IOC; caller handles that via specific file checks.\n    if (name === 'yarn.lock' || name === 'pnpm-lock.yaml' || name === 'pnpm-lock.yml' || name === 'composer.lock' || name === 'gemfile.lock' || name === 'poetry.lock' || name === 'cargo.lock')\n      return false;\n  }\n\n  return (\n    name.endsWith('.log') ||\n    name.endsWith('.txt') ||\n    name.endsWith('.json') ||\n    name.endsWith('.yaml') ||\n    name.endsWith('.yml') ||\n    name.endsWith('.mjs') ||\n    name.endsWith('.js') ||\n    name.endsWith('.history') ||\n    name.endsWith('.ps1') ||\n    name.endsWith('.sh') ||\n    name.endsWith('.zsh') ||\n    name.endsWith('.bash') ||\n    name.endsWith('.py') ||\n    name.endsWith('.pyz') ||\n    name.endsWith('.plist') ||\n    name.endsWith('.service') ||\n    name === 'fish_history' ||\n    name === 'consolehost_history.txt' ||\n    name.startsWith('npm-debug') ||\n    name.startsWith('pnpm-debug') ||\n    name.startsWith('yarn-error') ||\n    name === 'gh-token-monitor'\n  );\n}\n\n// \u2500\u2500 File scanner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction scanFile(filePath) {\n  const file = readTextIfExists(filePath);\n  if (!file) return [];\n\n  const hits = scanTextForPatterns(file.body, MINI_SHAI_HULUD_PATTERNS);\n  return hits.map((hit) =&gt; ({\n    path: file.path,\n    size: file.size,\n    mtime: file.mtime.toISOString(),\n    ...hit,\n  }));\n}\n\nfunction groupByFile(hits) {\n  const byFile = new Map();\n  for (const hit of hits) {\n    const bucket = byFile.get(hit.path) ?? [];\n    bucket.push(hit);\n    byFile.set(hit.path, bucket);\n  }\n  return [...byFile.entries()].map(([filePath, fileHits]) =&gt; ({\n    path: filePath,\n    hits: fileHits,\n  }));\n}\n\n// \u2500\u2500 Payload file existence check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// These files should not normally exist \u2014 their presence alone is a critical IOC.\n\nfunction checkPayloadFileExists(filePath) {\n  if (!fs.existsSync(filePath)) return null;\n  try {\n    const stats = fs.statSync(filePath);\n    return {\n      path: filePath,\n      size: stats.size,\n      mtime: stats.mtime.toISOString(),\n      id: 'file-exists-' + path.basename(filePath).replace(/\\W+/g, '-'),\n      label: `[FILE EXISTS] ${path.basename(filePath)} \u2014 worm payload detected`,\n      category: 'filesystem',\n      severity: 'critical',\n      match: path.basename(filePath),\n      snippet: `File exists: ${filePath} (${stats.size} bytes)`,\n    };\n  } catch {\n    return null;\n  }\n}\n\n// \u2500\u2500 Process list check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction checkSuspiciousProcesses(C) {\n  const processHits = [];\n  const suspicious = ['tanstack_runner', 'router_runtime', 'gh-token-monitor'];\n\n  try {\n    const cmd = process.platform === 'win32' ? 'tasklist /FO CSV /NH' : 'ps aux';\n    const output = execSync(cmd, { timeout: 5000, encoding: 'utf8' }).toLowerCase();\n\n    for (const proc of suspicious) {\n      if (output.includes(proc.toLowerCase())) {\n        processHits.push({\n          path: '[process list]',\n          size: 0,\n          mtime: new Date().toISOString(),\n          id: 'process-' + proc.replace(/\\W+/g, '-'),\n          label: `[PROCESS RUNNING] ${proc} \u2014 worm/daemon active`,\n          category: 'persistence',\n          severity: 'critical',\n          match: proc,\n          snippet: `Process \"${proc}\" found in running process list`,\n        });\n      }\n    }\n\n    // Unexpected bun execution (not in standard package manager positions)\n    if (/\\bbun\\b/.test(output) &amp;&amp; !output.includes('bun-as-installer')) {\n      processHits.push({\n        path: '[process list]',\n        size: 0,\n        mtime: new Date().toISOString(),\n        id: 'process-bun-unexpected',\n        label: '[PROCESS RUNNING] bun \u2014 unexpected Bun runtime (evasion technique)',\n        category: 'execution',\n        severity: 'high',\n        match: 'bun',\n        snippet: 'Bun runtime process found running \u2014 may indicate worm preinstall hook activity',\n      });\n    }\n  } catch {\n    // Process list not available \u2014 not a fatal error\n  }\n\n  return processHits;\n}\n\n// \u2500\u2500 Core scan orchestration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildScanTargets(opts) {\n  const defaults = getDefaultTargets();\n  const fileTargets = [...defaults.files];\n  const dirTargets = [...defaults.dirs];\n\n  if (opts.root) {\n    const resolvedRoot = path.resolve(opts.root);\n    if (fs.existsSync(resolvedRoot)) {\n      const stats = fs.statSync(resolvedRoot);\n      if (stats.isDirectory()) {\n        dirTargets.push(resolvedRoot);\n      } else if (stats.isFile()) {\n        fileTargets.push(resolvedRoot);\n      }\n    }\n  }\n\n  return { files: fileTargets, dirs: dirTargets };\n}\n\nfunction buildAudit(opts) {\n  const { C, log } = createLogger();\n\n  log.title('MINI SHAI-HULUD AUDIT \u2014 CVE-2026-45321');\n  console.log(`  ${C.red}${C.bold}\u26a0  DO NOT revoke tokens before isolating the machine  \u26a0${C.reset}`);\n  console.log(`  ${C.dim}${ADVISORY_REF}${C.reset}\\n`);\n\n  const targets = buildScanTargets(opts);\n\n  // \u2500\u2500 Step 1: Payload file existence (presence alone = critical) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  log.step('Checking for payload files (existence check)...');\n  const payloadNames = ['setup_bun.js', 'bun_environment.js', 'router_init.js', 'router_runtime.js', 'tanstack_runner.js', 'transformers.pyz'];\n  const payloadExistenceHits = targets.files\n    .filter((f) =&gt; payloadNames.includes(path.basename(f)))\n    .map(checkPayloadFileExists)\n    .filter(Boolean);\n\n  // \u2500\u2500 Step 2: Process list check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  log.step('Checking running processes...');\n  const processHits = checkSuspiciousProcesses(C);\n\n  // \u2500\u2500 Step 3: Scan known files for IOC patterns \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  log.step('Scanning targeted files for IOC patterns...');\n  const knownFileHits = [];\n  const progress = makeProgress(targets.files.length, C);\n  for (const filePath of targets.files) {\n    progress.tick(path.basename(filePath));\n    knownFileHits.push(...scanFile(filePath));\n  }\n\n  // \u2500\u2500 Step 4: Walk evidence directories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  log.step('Walking evidence directories...');\n  const walkedFiles = walkFiles(targets.dirs, {\n    maxDepth: Number.isFinite(opts.maxDepth) ? opts.maxDepth : 4,\n    filter: (f) =&gt; isTextLikeEvidence(f, opts),\n  });\n  const walkedHits = [];\n  const walkProgress = makeProgress(walkedFiles.length, C);\n  for (const filePath of walkedFiles) {\n    walkProgress.tick(path.basename(filePath));\n    walkedHits.push(...scanFile(filePath));\n  }\n\n  // \u2500\u2500 Aggregate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const allHits = dedupeByKey([...payloadExistenceHits, ...processHits, ...knownFileHits, ...walkedHits], (hit) =&gt; `${hit.path}|${hit.id}|${hit.match}|${hit.snippet}`);\n  const filesWithHits = groupByFile(allHits);\n  const criticalHits = allHits.filter((hit) =&gt; hit.severity === 'critical');\n  const scannedFiles = dedupeByKey([...targets.files, ...walkedFiles], (f) =&gt; f);\n\n  const summary = {\n    targetedFiles: targets.files.length,\n    scannedFiles: scannedFiles.length,\n    filesWithHits: filesWithHits.length,\n    totalHits: allHits.length,\n    criticalHits: criticalHits.length,\n    categories: allHits.reduce((acc, hit) =&gt; {\n      acc[hit.category] = (acc[hit.category] ?? 0) + 1;\n      return acc;\n    }, {}),\n  };\n\n  return {\n    C,\n    log,\n    summary,\n    filesWithHits,\n    criticalHits,\n    report: {\n      auditDate: new Date().toISOString(),\n      advisory: ADVISORY_REF,\n      scope: {\n        root: opts.root ? path.resolve(opts.root) : null,\n        host: os.hostname(),\n        platform: process.platform,\n      },\n      summary,\n      findings: allHits,\n      filesWithHits,\n    },\n  };\n}\n\n// \u2500\u2500 CSV serialisation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildCsvRows(result) {\n  const { summary, report, filesWithHits } = result;\n  const rows = [\n    {\n      recordType: 'summary',\n      auditDate: report.auditDate,\n      host: report.scope.host,\n      platform: report.scope.platform,\n      root: report.scope.root,\n      checkType: ADVISORY_REF,\n      targetedFiles: summary.targetedFiles,\n      scannedFiles: summary.scannedFiles,\n      filesWithHits: summary.filesWithHits,\n      totalHits: summary.totalHits,\n      criticalHits: summary.criticalHits,\n      categories: JSON.stringify(summary.categories),\n    },\n  ];\n\n  for (const file of filesWithHits) {\n    for (const hit of file.hits) {\n      rows.push({\n        recordType: 'finding',\n        auditDate: report.auditDate,\n        host: report.scope.host,\n        platform: report.scope.platform,\n        root: report.scope.root,\n        checkType: ADVISORY_REF,\n        path: file.path,\n        size: hit.size,\n        mtime: hit.mtime,\n        id: hit.id,\n        label: hit.label,\n        category: hit.category,\n        severity: hit.severity,\n        match: hit.match,\n        snippet: hit.snippet,\n      });\n    }\n  }\n\n  return rows;\n}\n\n// \u2500\u2500 Terminal report \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction printReport(result) {\n  const { C, log, summary, filesWithHits } = result;\n\n  log.step('Results:');\n\n  if (summary.criticalHits === 0) {\n    log.ok(\n      'CLEAR \u2014 no critical Mini Shai-Hulud IOCs detected.\\n' + '  Checked: payload files, persistence artefacts, C2 domains, package versions,\\n' + '           distinctive strings, daemon processes.',\n    );\n    console.log(`\\n  \ud83d\udcc4 ${result.reportPath}  (${summary.scannedFiles} files scanned)\\n`);\n    return;\n  }\n\n  // \u2500\u2500 Critical IOCs found \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const criticalFiles = filesWithHits.filter((f) =&gt; f.hits.some((h) =&gt; h.severity === 'critical'));\n\n  console.log(`\\n${C.red}${C.bold}\u2501\u2501\u2501 CRITICAL IOCs DETECTED \u2014 Mini Shai-Hulud \u2501\u2501\u2501${C.reset}`);\n  console.log(`${C.red}${C.bold}  CVE-2026-45321 / GHSA-g7cv-rxg3-hmpx \u2014 CVSS 9.6${C.reset}\\n`);\n\n  for (const file of criticalFiles) {\n    console.log(`  ${C.bold}${file.path}${C.reset}`);\n    for (const hit of file.hits.filter((h) =&gt; h.severity === 'critical')) {\n      console.log(`    ${C.red}${hit.label}${C.reset}  ${C.dim}(${hit.category})${C.reset}`);\n      if (hit.snippet) {\n        console.log(`    ${C.dim}${hit.snippet}${C.reset}`);\n      }\n    }\n    console.log('');\n  }\n\n  console.log(`${C.red}${C.bold}\u2501\u2501\u2501 INCIDENT RESPONSE \u2014 MANDATORY ORDER \u2501\u2501\u2501${C.reset}`);\n  console.log(`\\n  ${C.red}${C.bold}\u26a0  DO NOT revoke tokens before step 3  \u26a0${C.reset}`);\n  console.log(`  ${C.dim}The worm watchdog triggers rm -rf ~/ on token revocation (HTTP 40X).${C.reset}\\n`);\n  console.log(`  ${C.red}1.${C.reset} ISOLATE  \u2014 disconnect machine from network immediately`);\n  console.log(`  ${C.red}2.${C.reset} IMAGE    \u2014 forensic disk image before any cleanup`);\n  console.log(`  ${C.red}3.${C.reset} KILL     \u2014 disable gh-token-monitor daemon and worm processes`);\n  console.log(`  ${C.red}4.${C.reset} REVOKE   \u2014 npm, GitHub PAT/OAuth, AWS, Azure, GCP, Kubernetes, SSH`);\n  console.log(`  ${C.red}5.${C.reset} ROTATE   \u2014 all credentials reachable from this host`);\n  console.log(`  ${C.red}6.${C.reset} AUDIT    \u2014 AWS CloudTrail, Azure Activity Logs, GCP Audit Logs`);\n  console.log(`  ${C.red}7.${C.reset} VERIFY   \u2014 GitHub account: recent Dune-themed public repos, new PATs\\n`);\n  console.log(`  \ud83d\udcc4 ${result.reportPath}  (full execution trace inside)\\n`);\n}\n\n// \u2500\u2500 Entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function main() {\n  const opts = parseArgs(process.argv);\n\n  if (opts.help) {\n    showHelp();\n    process.exit(0);\n  }\n\n  const result = buildAudit(opts);\n  result.reportPath = opts.output;\n  writeReportFile(opts.output, result.report, buildCsvRows(result), CSV_COLUMNS);\n  printReport(result);\n}\n\nmain().catch((err) =&gt; {\n  const { log } = createLogger();\n  log.alert(`Fatal: ${err.message}`);\n  process.exit(1);\n});\n", "creation_timestamp": "2026-05-18T20:22:18.000000Z"}, {"uuid": "9a675a22-b555-46dd-8a3a-d1f8213aa871", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "published-proof-of-concept", "source": "Telegram/p6_7Fzr7AE5-s9SdgqzAFTlpxGf9IMuh2DhHzRrKndjq5KI", "content": "", "creation_timestamp": "2026-05-18T21:00:03.000000Z"}, {"uuid": "2648544c-b880-417c-af79-7055a59d8e9a", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-45321", "type": "seen", "source": "Telegram/Nb7K5zYmouklHSq2-4YzAjIlBSlI43wf9hUI3PLTIzXqPwQ", "content": "", "creation_timestamp": "2026-05-21T03:00:06.000000Z"}]}