{"uuid": "dac12874-baec-456c-8149-12e040ed3e34", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "GHSA-g7cv-rxg3-hmpx", "type": "seen", "source": "https://gist.github.com/maskeynihal/e780ed1e48c56592fc6612591a4bd420", "content": "#!/usr/bin/env bash\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# detect-mini-shai-hulud.sh\n#\n# Scans every git project under the CWD across ALL branches (local + already\n# fetched remote-tracking branches) for npm packages compromised in the\n# \"Mini Shai-Hulud\" supply-chain attack (npm ecosystem, 2026).\n#\n# For every watched package found in any lockfile on any branch, the script\n# reports the project, branch, package, version, and whether that version is\n# in the known-bad set.\n#\n# Compromised package list source: StepSecurity blog\n#   https://www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem\n# @tanstack/* patched versions cross-verified against GitHub Security Advisory:\n#   https://github.com/TanStack/router/security/advisories/GHSA-g7cv-rxg3-hmpx\n#\n# Usage:\n#   cd ~/projects\n#   bash detect-mini-shai-hulud.sh           # scan all repos under CWD\n#   bash detect-mini-shai-hulud.sh /some/dir # scan repos under /some/dir\n#\n# Scans: package-lock.json, yarn.lock, pnpm-lock.yaml in tracked tree of every\n#        branch (skips node_modules; never checks anything out).\n# Note:  bun.lockb is binary \u2014 for Bun projects:  bun pm ls | less\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nset -euo pipefail\n\n# \u2500\u2500\u2500 Compromised packages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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# Format: \"|[,,...]|\"\n# Add new entries here as advisories drop.\nVULN_LIST=(\n  \"@uipath/docsai-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/packager-tool-apiworkflow|0.0.19|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/packager-tool-workflowcompiler-browser|0.0.34|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/packager-tool-functions|0.1.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/agent.sdk|0.0.18|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/filesystem|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/admin-tool|0.1.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/llmgw-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tanstack/arktype-adapter|1.166.12,1.166.15|upgrade to 1.166.16\"\n  \"@tanstack/eslint-plugin-router|1.161.9,1.161.12|upgrade to 1.161.13\"\n  \"@tanstack/eslint-plugin-start|0.0.4,0.0.7|upgrade to 0.0.8\"\n  \"@tanstack/history|1.161.9,1.161.12|upgrade to 1.161.13\"\n  \"@tanstack/nitro-v2-vite-plugin|1.154.12,1.154.15|upgrade to 1.154.16\"\n  \"@tanstack/react-router|1.169.5,1.169.8|upgrade to 1.169.9\"\n  \"@tanstack/react-router-devtools|1.166.16,1.166.19|upgrade to 1.166.20\"\n  \"@tanstack/react-router-ssr-query|1.166.15,1.166.18|upgrade to 1.166.19\"\n  \"@tanstack/react-start|1.167.68,1.167.71|upgrade to 1.167.72\"\n  \"@tanstack/react-start-client|1.166.51,1.166.54|upgrade to 1.166.55\"\n  \"@tanstack/react-start-rsc|0.0.47,0.0.50|upgrade to 0.0.51\"\n  \"@tanstack/react-start-server|1.166.55,1.166.58|upgrade to 1.166.59\"\n  \"@tanstack/router-cli|1.166.46,1.166.49|upgrade to 1.166.50\"\n  \"@tanstack/router-core|1.169.5,1.169.8|upgrade to 1.169.9\"\n  \"@tanstack/router-devtools|1.166.16,1.166.19|upgrade to 1.166.20\"\n  \"@tanstack/router-devtools-core|1.167.6,1.167.9|upgrade to 1.167.10\"\n  \"@tanstack/router-generator|1.166.45,1.166.48|upgrade to 1.166.49\"\n  \"@tanstack/router-plugin|1.167.38,1.167.41|upgrade to 1.167.42\"\n  \"@tanstack/router-ssr-query-core|1.168.3,1.168.6|upgrade to 1.168.7\"\n  \"@tanstack/router-utils|1.161.11,1.161.14|upgrade to 1.161.15\"\n  \"@tanstack/router-vite-plugin|1.166.53,1.166.56|upgrade to 1.166.57\"\n  \"@tanstack/solid-router|1.169.5,1.169.8|upgrade to 1.169.9\"\n  \"@tanstack/solid-router-devtools|1.166.16,1.166.19|upgrade to 1.166.20\"\n  \"@tanstack/solid-router-ssr-query|1.166.15,1.166.18|upgrade to 1.166.19\"\n  \"@tanstack/solid-start|1.167.65,1.167.68|upgrade to 1.167.69\"\n  \"@tanstack/solid-start-client|1.166.50,1.166.53|upgrade to 1.166.54\"\n  \"@tanstack/solid-start-server|1.166.54,1.166.57|upgrade to 1.166.58\"\n  \"@tanstack/start-client-core|1.168.5,1.168.8|upgrade to 1.168.9\"\n  \"@tanstack/start-fn-stubs|1.161.9,1.161.12|upgrade to 1.161.13\"\n  \"@tanstack/start-plugin-core|1.169.23,1.169.26|upgrade to 1.169.27\"\n  \"@tanstack/start-server-core|1.167.33,1.167.36|upgrade to 1.167.37\"\n  \"@tanstack/start-static-server-functions|1.166.44,1.166.47|upgrade to 1.166.48\"\n  \"@tanstack/start-storage-context|1.166.38,1.166.41|upgrade to 1.166.42\"\n  \"@tanstack/valibot-adapter|1.166.12,1.166.15|upgrade to 1.166.16\"\n  \"@tanstack/virtual-file-routes|1.161.10,1.161.13|upgrade to 1.161.14\"\n  \"@tanstack/vue-router|1.169.5,1.169.8|upgrade to 1.169.9\"\n  \"@tanstack/vue-router-devtools|1.166.16,1.166.19|upgrade to 1.166.20\"\n  \"@tanstack/vue-router-ssr-query|1.166.15,1.166.18|upgrade to 1.166.19\"\n  \"@tanstack/vue-start|1.167.61,1.167.64|upgrade to 1.167.65\"\n  \"@tanstack/vue-start-client|1.166.46,1.166.49|upgrade to 1.166.50\"\n  \"@tanstack/vue-start-server|1.166.50,1.166.53|upgrade to 1.166.54\"\n  \"@tanstack/zod-adapter|1.166.12,1.166.15|upgrade to 1.166.16\"\n  \"@draftauth/client|0.2.1,0.2.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@draftauth/core|0.13.1,0.13.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@draftlab/auth|0.24.1,0.24.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@draftlab/auth-router|0.5.1,0.5.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@draftlab/db|0.16.1,0.16.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@taskflow-corp/cli|0.1.24,0.1.25,0.1.26,0.1.27,0.1.28,0.1.29|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tolka/cli|1.0.2,1.0.3,1.0.4,1.0.6|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/access-policy-sdk|0.3.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/access-policy-tool|0.3.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/agent-sdk|1.0.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/agent-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/aops-policy-tool|0.3.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/ap-chat|1.5.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/api-workflow-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/apollo-core|5.9.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/apollo-react|4.24.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/apollo-wind|2.16.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/auth|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/case-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/cli|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/codedagent-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/codedagents-tool|0.1.12|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/codedapp-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/common|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/context-grounding-tool|0.1.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/data-fabric-tool|1.0.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/flow-tool|1.0.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/functions-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/gov-tool|0.3.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/identity-tool|0.1.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/insights-sdk|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/insights-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/integrationservice-sdk|1.0.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/integrationservice-tool|1.0.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/maestro-sdk|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/maestro-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/orchestrator-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/packager-tool-bpmn|0.0.9|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/packager-tool-case|0.0.9|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/packager-tool-connector|0.0.19|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/packager-tool-flow|0.0.19|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/packager-tool-webapp|1.0.6|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/packager-tool-workflowcompiler|0.0.16|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/platform-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/project-packager|1.1.16|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/resource-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/resourcecatalog-tool|0.1.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/resources-tool|0.1.11|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/robot|1.3.4|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/rpa-legacy-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/rpa-tool|0.9.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/solution-packager|0.0.35|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/solution-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/solutionpackager-sdk|1.0.11|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/solutionpackager-tool-core|0.0.34|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/tasks-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/telemetry|0.0.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/test-manager-tool|1.0.2|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/tool-workflowcompiler|0.0.12|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/traces-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/ui-widgets-multi-file-upload|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/uipath-python-bridge|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/vertical-solutions-tool|1.0.1|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/vss|0.1.6|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@uipath/widget.sdk|1.2.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"safe-action|0.8.3,0.8.4|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@supersurkhet/cli|0.0.2,0.0.3,0.0.4,0.0.5,0.0.6,0.0.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@supersurkhet/sdk|0.0.2,0.0.3,0.0.4,0.0.5,0.0.6,0.0.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"cmux-agent-mcp|0.1.3,0.1.4,0.1.5,0.1.6,0.1.7,0.1.8|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"git-git-git|1.0.8,1.0.9,1.0.10,1.0.12|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"git-branch-selector|1.3.3,1.3.4,1.3.5,1.3.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"nextmove-mcp|0.1.3,0.1.4,0.1.5,0.1.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@beproduct/nestjs-auth|0.1.2,0.1.3,0.1.4,0.1.5,0.1.6,0.1.7,0.1.8,0.1.9,0.1.10,0.1.11,0.1.12,0.1.13,0.1.14,0.1.15,0.1.16,0.1.17,0.1.19|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@dirigible-ai/sdk|0.6.2,0.6.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@ml-toolkit-ts/preprocessing|1.0.2,1.0.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@ml-toolkit-ts/xgboost|1.0.3,1.0.4|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"agentwork-cli|0.1.4,0.1.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"ml-toolkit-ts|1.0.4,1.0.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/airport-data|0.7.4,0.7.5,0.7.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/airports|0.6.2,0.6.3,0.6.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/airspace|0.8.1,0.8.2,0.8.4|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/airspace-data|0.5.3,0.5.4,0.5.6|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/airway-data|0.5.4,0.5.5,0.5.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/airways|0.4.2,0.4.3,0.4.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/fix-data|0.6.4,0.6.5,0.6.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/fixes|0.3.2,0.3.3,0.3.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/flight-math|0.5.4,0.5.5,0.5.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/flightplan|0.5.2,0.5.3,0.5.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/geo|0.4.4,0.4.5,0.4.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/icao-registry|0.5.2,0.5.3,0.5.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/icao-registry-data|0.8.4,0.8.5,0.8.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/mcp|0.9.1,0.9.2,0.9.4|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/navaid-data|0.6.4,0.6.5,0.6.7|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/navaids|0.4.2,0.4.3,0.4.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/notams|0.3.6,0.3.7,0.3.9|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/procedure-data|0.7.3,0.7.4,0.7.6|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/procedures|0.5.2,0.5.3,0.5.5|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/types|0.8.1,0.8.2,0.8.4|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/units|0.4.3,0.4.4,0.4.6|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@squawk/weather|0.5.6,0.5.7,0.5.9|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"wot-api|0.8.1,0.8.2,0.8.4|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"cross-stitch|1.1.3,1.1.4,1.1.6|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"ts-dna|3.0.1,3.0.2,3.0.4|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tallyui/components|1.0.1,1.0.2,1.0.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tallyui/connector-medusa|1.0.1,1.0.2,1.0.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tallyui/connector-shopify|1.0.1,1.0.2,1.0.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tallyui/connector-vendure|1.0.1,1.0.2,1.0.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tallyui/connector-woocommerce|1.0.1,1.0.2,1.0.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tallyui/core|0.2.1,0.2.2,0.2.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tallyui/database|1.0.1,1.0.2,1.0.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tallyui/pos|0.1.1,0.1.2,0.1.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tallyui/storage-sqlite|0.2.1,0.2.2,0.2.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@tallyui/theme|0.2.1,0.2.2,0.2.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@mesadev/rest|0.28.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@mesadev/saguaro|0.4.22|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@mesadev/sdk|0.28.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@mistralai/mistralai|2.2.3,2.2.4|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@mistralai/mistralai-azure|1.7.2,1.7.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n  \"@mistralai/mistralai-gcp|1.7.2,1.7.3|remove/replace \u2014 no known clean patched version; rotate any creds touched at install\"\n)\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nVULN_RAW=$(printf '%s\\n' \"${VULN_LIST[@]}\")\nexport VULN_RAW\nexport SCAN_ROOT=\"${1:-$(pwd)}\"\n\npython3 &lt;&lt;'PYEND'\nimport json\nimport os\nimport re\nimport subprocess\nimport sys\nfrom pathlib import Path\n\nraw = os.environ[\"VULN_RAW\"]\nvuln = {}\nfor line in raw.strip().split(\"\\n\"):\n    pkg, bad_csv, fix = line.split(\"|\", 2)\n    vuln[pkg] = {\"bad\": set(bad_csv.split(\",\")), \"fix\": fix}\n\nwatch = set(vuln.keys())\npkg_re_alt = \"|\".join(re.escape(p) for p in watch)\n\nroot = Path(os.environ[\"SCAN_ROOT\"]).resolve()\n\nSKIP_DIRS = {\n    \"node_modules\", \".next\", \"dist\", \"build\", \".venv\", \"venv\",\n    \"__pycache__\", \".turbo\", \".cache\", \".pnpm-store\", \"target\",\n}\n\ndef discover_repos(root: Path):\n    repos = []\n    for dirpath, dirnames, filenames in os.walk(root, followlinks=False):\n        if \".git\" in dirnames or \".git\" in filenames:\n            repos.append(Path(dirpath))\n            dirnames.clear()\n            continue\n        dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS and not d.startswith(\".\")]\n    return sorted(repos)\n\nLOCK_RE = re.compile(r\"(?:^|/)(package-lock\\.json|yarn\\.lock|pnpm-lock\\.yaml)$\")\n\ndef run(args, cwd=None, binary=False):\n    return subprocess.run(\n        args,\n        cwd=cwd,\n        capture_output=True,\n        text=not binary,\n        errors=\"replace\" if not binary else None,\n    )\n\ndef list_branches(repo: Path):\n    r = run(\n        [\n            \"git\", \"for-each-ref\",\n            \"--format=%(refname)|%(refname:short)\",\n            \"refs/heads\", \"refs/remotes\",\n        ],\n        cwd=repo,\n    )\n    if r.returncode != 0:\n        return []\n    out = []\n    for line in r.stdout.strip().split(\"\\n\"):\n        if not line:\n            continue\n        full, short = line.split(\"|\", 1)\n        if full.startswith(\"refs/remotes/\") and full.endswith(\"/HEAD\"):\n            continue\n        out.append((full, short))\n    return out\n\ndef list_lockfiles_with_sha(repo: Path, ref: str):\n    r = run([\"git\", \"ls-tree\", \"-r\", ref], cwd=repo)\n    if r.returncode != 0:\n        return []\n    out = []\n    for line in r.stdout.split(\"\\n\"):\n        if not line:\n            continue\n        try:\n            meta, name = line.split(\"\\t\", 1)\n        except ValueError:\n            continue\n        parts = meta.split(\" \")\n        if len(parts) != 3:\n            continue\n        _, typ, sha = parts\n        if typ != \"blob\":\n            continue\n        if \"/node_modules/\" in name or name.startswith(\"node_modules/\"):\n            continue\n        if LOCK_RE.search(name):\n            out.append((name, sha))\n    return out\n\ndef read_blob(repo: Path, sha: str):\n    r = run([\"git\", \"cat-file\", \"-p\", sha], cwd=repo)\n    if r.returncode != 0:\n        return None\n    return r.stdout\n\ndef parse_lock(kind: str, content: str):\n    found = set()\n\n    def add(p, v):\n        if p in watch and v:\n            found.add((p, v))\n\n    if kind == \"package-lock.json\":\n        try:\n            d = json.loads(content)\n        except json.JSONDecodeError:\n            return found\n        pkg_suffix_re = re.compile(r\"node_modules/(\" + pkg_re_alt + r\")$\")\n        for key, meta in (d.get(\"packages\") or {}).items():\n            m = pkg_suffix_re.search(key)\n            if m:\n                add(m.group(1), meta.get(\"version\", \"\"))\n\n        def walk(deps):\n            for name, meta in (deps or {}).items():\n                if name in watch:\n                    add(name, meta.get(\"version\", \"\"))\n                walk(meta.get(\"dependencies\"))\n\n        walk(d.get(\"dependencies\"))\n\n    elif kind == \"yarn.lock\":\n        blocks = re.split(r\"\\n(?=[^\\s])\", content)\n        header_pkg_re = re.compile(r'\"?(' + pkg_re_alt + r\")@\")\n        ver_line_re = re.compile(r'^\\s+version\\s+\"([^\"]+)\"', re.M)\n        for block in blocks:\n            first = block.split(\"\\n\", 1)[0]\n            pkgs = header_pkg_re.findall(first)\n            if not pkgs:\n                continue\n            vm = ver_line_re.search(block)\n            if not vm:\n                continue\n            ver = vm.group(1)\n            for p in set(pkgs):\n                add(p, ver)\n\n    elif kind == \"pnpm-lock.yaml\":\n        occ_re = re.compile(r\"(\" + pkg_re_alt + r\")@([0-9][0-9a-zA-Z.+-]*)\")\n        for m in occ_re.finditer(content):\n            add(m.group(1), m.group(2))\n\n    return found\n\nrepos = discover_repos(root)\nprint(\n    f\"Scanning {len(repos)} git repo(s) under {root}, across all branches, \"\n    f\"for {len(watch)} watched packages...\\n\",\n    flush=True,\n)\n\nresults = []\nblob_cache = {}\nvulnerable_branches = set()\nscanned_projects = []\nprojects_with_hits = set()\n\nfor repo in repos:\n    rel_project = str(repo.relative_to(root)) if repo != root else \".\"\n    branches = list_branches(repo)\n    if not branches:\n        continue\n    scanned_projects.append(rel_project)\n    print(f\"\u2022 {rel_project}  ({len(branches)} ref(s))\", flush=True)\n\n    for full_ref, short_ref in branches:\n        for lock_path, blob_sha in list_lockfiles_with_sha(repo, full_ref):\n            base = os.path.basename(lock_path)\n\n            cache_key = (blob_sha, base)\n            if cache_key in blob_cache:\n                hits = blob_cache[cache_key]\n            else:\n                content = read_blob(repo, blob_sha)\n                hits = parse_lock(base, content) if content else set()\n                blob_cache[cache_key] = hits\n\n            for pkg, ver in hits:\n                is_vuln = ver in vuln[pkg][\"bad\"]\n                results.append({\n                    \"project\": rel_project,\n                    \"branch\": short_ref,\n                    \"lockfile\": lock_path,\n                    \"package\": pkg,\n                    \"version\": ver,\n                    \"bad_versions\": \", \".join(sorted(vuln[pkg][\"bad\"])),\n                    \"vulnerable\": is_vuln,\n                    \"fix\": vuln[pkg][\"fix\"],\n                })\n                projects_with_hits.add(rel_project)\n                if is_vuln:\n                    vulnerable_branches.add((rel_project, short_ref))\n\nprint()\nprint(\"\u2500\" * 78)\n\nif not scanned_projects:\n    print(\"No git repositories with branches found under \" + str(root))\n    sys.exit(0)\n\nseen = set()\nunique = []\nfor r in results:\n    key = (r[\"project\"], r[\"branch\"], r[\"package\"], r[\"version\"])\n    if key in seen:\n        continue\n    seen.add(key)\n    unique.append(r)\n\nCLEAN_MSG = \"no vulnerable package installed\"\nclean_projects = [p for p in scanned_projects if p not in projects_with_hits]\nfor project in clean_projects:\n    unique.append({\n        \"project\": project,\n        \"branch\": \"\u2014\",\n        \"package\": CLEAN_MSG,\n        \"version\": \"\u2014\",\n        \"bad_versions\": \"\u2014\",\n        \"vulnerable\": False,\n        \"_clean\": True,\n    })\n\ndef sort_key(r):\n    clean = r.get(\"_clean\", False)\n    return (\n        not r[\"vulnerable\"],\n        clean,\n        r[\"project\"],\n        r[\"branch\"],\n        r[\"package\"],\n        r[\"version\"],\n    )\n\nunique.sort(key=sort_key)\n\nheaders = [\"Project\", \"Branch\", \"Package\", \"Version\", \"Bad Versions\", \"Vulnerable\"]\nrows = [\n    [\n        r[\"project\"],\n        r[\"branch\"],\n        r[\"package\"],\n        r[\"version\"],\n        r[\"bad_versions\"],\n        \"YES\" if r[\"vulnerable\"] else \"no\",\n    ]\n    for r in unique\n]\n\nwidths = [len(h) for h in headers]\nfor row in rows:\n    for i, cell in enumerate(row):\n        widths[i] = max(widths[i], len(cell))\n\ndef hr():\n    return \"+\" + \"+\".join(\"-\" * (w + 2) for w in widths) + \"+\"\n\ndef fmt(row):\n    return \"| \" + \" | \".join(cell.ljust(widths[i]) for i, cell in enumerate(row)) + \" |\"\n\nprint()\nprint(hr())\nprint(fmt(headers))\nprint(hr().replace(\"-\", \"=\"))\nfor row in rows:\n    print(fmt(row))\nprint(hr())\n\nvuln_count = sum(1 for r in unique if r[\"vulnerable\"])\nhit_count = sum(1 for r in unique if not r.get(\"_clean\", False))\nprint()\nprint(f\"Projects scanned:          {len(scanned_projects)}\")\nprint(f\"Projects with hits:        {len(projects_with_hits)}\")\nprint(f\"Clean projects:            {len(clean_projects)}\")\nprint(f\"Watched-package matches:   {hit_count}\")\nprint(f\"Vulnerable matches:        {vuln_count}\")\nprint(f\"Vulnerable (project,branch) pairs: {len(vulnerable_branches)}\")\n\nif vuln_count:\n    print()\n    print(\"Fix actions for vulnerable packages:\")\n    seen_fix = {}\n    for r in unique:\n        if r[\"vulnerable\"]:\n            seen_fix.setdefault(r[\"package\"], r[\"fix\"])\n    for pkg, fix in sorted(seen_fix.items()):\n        print(f\"  \u2022 {pkg}: {fix}\")\n    print()\n    print(\"Remediation reminder:\")\n    print(\"  1. Upgrade/remove each affected package per the messages above.\")\n    print(\"  2. Delete node_modules and the lock file, then reinstall.\")\n    print(\"  3. Rotate any secrets accessible to your dev/build/CI environment\")\n    print(\"     (the worm exfiltrates env vars, .env files, cloud/GitHub/npm tokens, SSH keys).\")\n    print(\"  4. Audit CI logs for outbound calls to attacker-controlled GitHub repos.\")\nPYEND\n", "creation_timestamp": "2026-05-12T11:45:22.000000Z"}