Last active
November 24, 2025 14:40
-
-
Save Lp-Francois/9e70eb7488bcc8ec76e0e27ed1c738e5 to your computer and use it in GitHub Desktop.
Check for sha1hulud against a local repo. Run `curl -s https://gist.githubusercontent.com/Lp-Francois/9e70eb7488bcc8ec76e0e27ed1c738e5/raw/ca9b7e31797fb1421938aaead54ad2381572890f/check-sha1hulud-vuln.js | node`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| /** | |
| * SHA1Hulud Vulnerability Checker | |
| * | |
| * Checks if the workspace is affected by the SHA1Hulud vulnerability | |
| * as described in https://helixguard.ai/blog/malicious-sha1hulud-2025-11-24 | |
| * | |
| * Usage: node tools/check-sha1hulud-vuln.js | |
| */ | |
| const { execSync } = require('child_process'); | |
| const { readFileSync, existsSync } = require('fs'); | |
| const { join } = require('path'); | |
| // Debug mode - set DEBUG=true to enable verbose logging | |
| const DEBUG = process.env.DEBUG === 'true'; | |
| /** | |
| * Debug logging function | |
| */ | |
| function debugLog(...args) { | |
| if (DEBUG) { | |
| console.log('[DEBUG]', ...args); | |
| } | |
| } | |
| /** | |
| * Detect which package manager is being used | |
| * Returns: 'pnpm', 'yarn', 'npm', or null | |
| */ | |
| function detectPackageManager() { | |
| const cwd = process.cwd(); | |
| if (existsSync(join(cwd, 'pnpm-lock.yaml'))) { | |
| debugLog('Detected package manager: pnpm (found pnpm-lock.yaml)'); | |
| return 'pnpm'; | |
| } | |
| if (existsSync(join(cwd, 'yarn.lock'))) { | |
| debugLog('Detected package manager: yarn (found yarn.lock)'); | |
| return 'yarn'; | |
| } | |
| if (existsSync(join(cwd, 'package-lock.json'))) { | |
| debugLog('Detected package manager: npm (found package-lock.json)'); | |
| return 'npm'; | |
| } | |
| // Check package.json for packageManager field | |
| try { | |
| const packageJsonPath = join(cwd, 'package.json'); | |
| if (existsSync(packageJsonPath)) { | |
| const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); | |
| if (packageJson.packageManager) { | |
| const pm = packageJson.packageManager.split('@')[0]; | |
| debugLog(`Detected package manager from package.json: ${pm}`); | |
| return pm; | |
| } | |
| } | |
| } catch (error) { | |
| debugLog('Error reading package.json:', error.message); | |
| } | |
| debugLog('No lock file found, defaulting to package.json fallback'); | |
| return null; | |
| } | |
| // Vulnerable packages and versions from the blog post | |
| const VULNERABLE_PACKAGES = { | |
| '@zapier/zapier-sdk': ['0.15.5', '0.15.7'], | |
| '@posthog/core': ['1.5.6'], | |
| 'posthog-node': ['5.11.3', '5.13.3', '4.18.1'], | |
| '@asyncapi/specs': ['6.10.1', '6.8.2', '6.9.1', '6.8.3'], | |
| '@postman/tunnel-agent': ['0.6.6', '0.6.5'], | |
| 'posthog-react-native': ['4.12.5', '4.11.1'], | |
| '@asyncapi/parser': ['3.4.1', '3.4.2'], | |
| '@asyncapi/openapi-schema-parser': ['3.0.25'], | |
| '@asyncapi/avro-schema-parser': ['3.0.25', '3.0.26'], | |
| '@asyncapi/protobuf-schema-parser': ['3.6.1', '3.5.3'], | |
| '@asyncapi/react-component': ['2.6.6'], | |
| '@asyncapi/generator': ['2.8.5'], | |
| '@posthog/ai': ['7.1.2'], | |
| '@asyncapi/modelina': ['5.10.2', '5.10.3'], | |
| '@asyncapi/generator-react-sdk': ['1.1.4', '1.1.5'], | |
| '@postman/csv-parse': ['4.0.3', '4.0.4', '4.0.5'], | |
| 'posthog-react-native-session-replay': ['1.2.2'], | |
| '@asyncapi/converter': ['1.6.3'], | |
| '@asyncapi/multi-parser': ['2.2.1', '2.2.2'], | |
| '@posthog/cli': ['0.5.15'], | |
| '@zapier/secret-scrubber': ['1.1.3', '1.1.4', '1.1.5'], | |
| 'zapier-platform-schema': ['18.0.2'], | |
| 'zapier-platform-core': ['18.0.2', '18.0.3'], | |
| '@ensdomains/address-encoder': ['1.1.5'], | |
| '@ensdomains/content-hash': ['3.0.1'], | |
| 'crypto-addr-codec': ['0.1.9'], | |
| '@asyncapi/nunjucks-filters': ['2.1.1', '2.1.2'], | |
| '@asyncapi/bundler': ['0.6.5', '0.6.6'], | |
| '@posthog/nextjs-config': ['1.5.1'], | |
| '@asyncapi/html-template': ['3.3.2', '3.3.3'], | |
| '@asyncapi/diff': ['0.5.1', '0.5.2'], | |
| '@asyncapi/cli': ['4.1.2'], | |
| '@asyncapi/optimizer': ['1.0.5', '1.0.6'], | |
| '@asyncapi/modelina-cli': ['5.10.2', '5.10.3'], | |
| '@postman/aether-icons': ['2.23.2', '2.23.4'], | |
| '@asyncapi/generator-components': ['0.3.2'], | |
| '@asyncapi/generator-helpers': ['0.2.1', '0.2.2'], | |
| 'zapier-platform-cli': ['18.0.3'], | |
| '@posthog/rrweb': ['0.0.31'], | |
| 'ethereum-ens': ['0.8.1'], | |
| '@posthog/rrweb-utils': ['0.0.31'], | |
| '@posthog/rrweb-snapshot': ['0.0.31'], | |
| '@posthog/rrdom': ['0.0.31'], | |
| '@asyncapi/problem': ['1.0.1', '1.0.2'], | |
| '@postman/secret-scanner-wasm': ['2.1.3', '2.1.2', '2.1.4'], | |
| '@ensdomains/eth-ens-namehash': ['2.0.16'], | |
| 'posthog-docusaurus': ['2.0.6'], | |
| '@postman/pretty-ms': ['6.1.1', '6.1.3', '6.1.2'], | |
| 'web-types-lit': ['0.1.1'], | |
| 'mcp-use': ['1.4.2', '1.4.3'], | |
| '@posthog/react-rrweb-player': ['1.1.4'], | |
| '@asyncapi/markdown-template': ['1.6.8', '1.6.9'], | |
| '@ensdomains/buffer': ['0.1.2'], | |
| '@postman/node-keytar': ['7.9.4', '7.9.5', '7.9.6'], | |
| '@mcp-use/inspector': ['0.6.2', '0.6.3'], | |
| '@mcp-use/cli': ['2.2.6'], | |
| '@zapier/spectral-api-ruleset': ['1.9.1', '1.9.2', '1.9.3'], | |
| '@posthog/geoip-plugin': ['0.0.8'], | |
| '@ensdomains/dnsprovejs': ['0.5.3'], | |
| '@ensdomains/solsha1': ['0.0.4'], | |
| '@asyncapi/web-component': ['2.6.6', '2.6.7'], | |
| '@posthog/nuxt': ['1.2.9'], | |
| '@zapier/browserslist-config-zapier': ['1.0.3', '1.0.5'], | |
| '@posthog/wizard': ['1.18.1'], | |
| 'react-native-use-modal': ['1.0.3'], | |
| '@asyncapi/java-spring-template': ['1.6.1', '1.6.2'], | |
| '@posthog/rrweb-record': ['0.0.31'], | |
| '@posthog/siphash': ['1.1.2'], | |
| '@posthog/piscina': ['3.2.1'], | |
| '@ensdomains/ens-validation': ['0.1.1'], | |
| '@posthog/plugin-contrib': ['0.0.6'], | |
| '@posthog/agent': ['1.24.1'], | |
| '@postman/postman-mcp-server': ['2.4.11', '2.4.10'], | |
| '@asyncapi/nodejs-ws-template': ['0.10.1', '0.10.2'], | |
| '@actbase/react-daum-postcode': ['1.0.5'], | |
| 'token.js-fork': ['0.7.32'], | |
| '@postman/pm-bin-windows-x64': ['1.24.5', '1.24.4'], | |
| '@ensdomains/ens-avatar': ['1.0.4'], | |
| '@postman/pm-bin-linux-x64': ['1.24.3', '1.24.4', '1.24.5'], | |
| '@posthog/hedgehog-mode': ['0.0.42'], | |
| 'create-mcp-use-app': ['0.5.3', '0.5.4'], | |
| '@postman/pm-bin-macos-arm64': ['1.24.5', '1.24.3', '1.24.4'], | |
| '@posthog/nextjs': ['0.0.3'], | |
| '@postman/pm-bin-macos-x64': ['1.24.3', '1.24.5'], | |
| 'redux-router-kit': ['1.2.2', '1.2.3', '1.2.4'], | |
| '@ensdomains/dnssecoraclejs': ['0.2.9'], | |
| '@postman/mcp-ui-client': ['5.5.1', '5.5.2'], | |
| '@postman/postman-mcp-cli': ['1.0.5', '1.0.4'], | |
| '@zapier/babel-preset-zapier': ['6.4.1', '6.4.3'], | |
| '@ensdomains/thorin': ['0.6.51'], | |
| '@postman/postman-collection-fork': ['4.3.3', '4.3.4', '4.3.5'], | |
| '@asyncapi/nodejs-template': ['3.0.5'], | |
| '@postman/wdio-allure-reporter': ['0.0.9'], | |
| '@postman/wdio-junit-reporter': ['0.0.4', '0.0.6'], | |
| '@postman/final-node-keytar': ['7.9.1', '7.9.2'], | |
| 'zapier-async-storage': ['1.0.1', '1.0.2', '1.0.3'], | |
| '@ensdomains/test-utils': ['1.3.1'], | |
| '@ensdomains/hardhat-chai-matchers-viem': ['0.1.15'], | |
| '@asyncapi/java-spring-cloud-stream-template': ['0.13.5', '0.13.6'], | |
| '@zapier/eslint-plugin-zapier': ['11.0.3', '11.0.4', '11.0.5'], | |
| 'devstart-cli': ['1.0.6'], | |
| '@asyncapi/java-template': ['0.3.5', '0.3.6'], | |
| '@asyncapi/go-watermill-template': ['0.2.76', '0.2.77'], | |
| '@asyncapi/python-paho-template': ['0.2.14', '0.2.15'], | |
| '@ensdomains/hardhat-toolbox-viem-extended': ['0.0.6'], | |
| '@ensdomains/vite-plugin-i18next-loader': ['4.0.4'], | |
| 'zapier-platform-legacy-scripting-runner': ['4.0.3', '4.0.4'], | |
| '@asyncapi/server-api': ['0.16.25'], | |
| '@ensdomains/offchain-resolver-contracts': ['0.2.2'], | |
| '@zapier/ai-actions': ['0.1.18', '0.1.19', '0.1.20'], | |
| '@zapier/mcp-integration': ['3.0.1', '3.0.3'], | |
| '@ensdomains/ens-archived-contracts': ['0.0.3'], | |
| '@ensdomains/dnssec-oracle-anchors': ['0.0.2'], | |
| '@ensdomains/mock': ['2.1.52'], | |
| 'zapier-scripts': ['7.8.3', '7.8.4'], | |
| '@quick-start-soft/quick-task-refine': ['1.4.2511142126'], | |
| '@zapier/ai-actions-react': ['0.1.13', '0.1.14'], | |
| '@quick-start-soft/quick-git-clean-markdown': ['1.4.2511142126'], | |
| '@ensdomains/ui': ['3.4.6'], | |
| '@quick-start-soft/quick-markdown': ['1.4.2511142126'], | |
| '@zapier/stubtree': ['0.1.3'], | |
| '@ensdomains/unruggable-gateways': ['0.0.3'], | |
| '@posthog/rrweb-player': ['0.0.31'], | |
| '@asyncapi/dotnet-rabbitmq-template': ['1.0.1', '1.0.2'], | |
| '@ensdomains/react-ens-address': ['0.0.32'], | |
| '@asyncapi/php-template': ['0.1.1'], | |
| '@quick-start-soft/quick-document-translator': ['1.4.2511142126'], | |
| '@quick-start-soft/quick-markdown-image': ['1.4.2511142126'], | |
| '@strapbuild/react-native-date-time-picker': ['2.0.4'], | |
| 'github-action-for-generator': ['2.1.28'], | |
| '@actbase/react-kakaosdk': ['0.9.27'], | |
| 'bytecode-checker-cli': ['1.0.8', '1.0.9', '1.0.10'], | |
| '@markvivanco/app-version-checker': ['1.0.1', '1.0.2'], | |
| '@louisle2/cortex-js': ['0.1.6'], | |
| 'orbit-boxicons': ['2.1.3'], | |
| 'react-native-worklet-functions': ['3.3.3'], | |
| 'poper-react-sdk': ['0.1.2'], | |
| '@ensdomains/web3modal': ['1.10.2'], | |
| 'gate-evm-tools-test': ['1.0.5', '1.0.6', '1.0.7'], | |
| 'n8n-nodes-tmdb': ['0.5.1'], | |
| 'capacitor-plugin-purchase': ['0.1.1'], | |
| 'expo-audio-session': ['0.2.1'], | |
| 'capacitor-plugin-apptrackingios': ['0.0.21'], | |
| 'asyncapi-preview': ['1.0.1', '1.0.2'], | |
| '@actbase/react-absolute': ['0.8.3'], | |
| '@actbase/react-native-devtools': ['0.1.3'], | |
| '@posthog/variance-plugin': ['0.0.8'], | |
| '@posthog/twitter-followers-plugin': ['0.0.8'], | |
| 'medusa-plugin-momo': ['0.0.68'], | |
| 'scgs-capacitor-subscribe': ['1.0.11'], | |
| 'gate-evm-check-code2': ['2.0.3', '2.0.4', '2.0.5'], | |
| 'lite-serper-mcp-server': ['0.2.2'], | |
| '@asyncapi/edavisualiser': ['1.2.1', '1.2.2'], | |
| 'esbuild-plugin-eta': ['0.1.1'], | |
| '@ensdomains/server-analytics': ['0.0.2'], | |
| 'zuper-stream': ['2.0.9'], | |
| '@quick-start-soft/quick-markdown-compose': ['1.4.2506300029'], | |
| '@posthog/snowflake-export-plugin': ['0.0.8'], | |
| '@actbase/react-native-kakao-channel': ['1.0.2'], | |
| '@posthog/sendgrid-plugin': ['0.0.8'], | |
| 'evm-checkcode-cli': ['1.0.12', '1.0.13', '1.0.14'], | |
| '@ensdomains/subdomain-registrar': ['0.2.4'], | |
| 'claude-token-updater': ['1.0.3'], | |
| '@trigo/atrix-pubsub': ['4.0.3'], | |
| '@trigo/hapi-auth-signedlink': ['1.3.1'], | |
| '@strapbuild/react-native-perspective-image-cropper-poojan31': ['0.4.6'], | |
| 'axios-builder': ['1.2.1'], | |
| 'calc-loan-interest': ['1.0.4'], | |
| 'medusa-plugin-announcement': ['0.0.3'], | |
| open2internet: ['0.1.1'], | |
| '@ensdomains/cypress-metamask': ['1.2.1'], | |
| '@ensdomains/renewal': ['0.0.13'], | |
| 'cpu-instructions': ['0.0.14'], | |
| 'orbit-soap': ['0.43.13'], | |
| '@asyncapi/keeper': ['0.0.2', '0.0.3'], | |
| '@strapbuild/react-native-perspective-image-cropper-2': ['0.4.7'], | |
| '@actbase/react-native-actionsheet': ['1.0.3'], | |
| '@posthog/ingestion-alert-plugin': ['0.0.8'], | |
| '@actbase/react-native-simple-video': ['1.0.13'], | |
| '@actbase/react-native-kakao-navi': ['2.0.4'], | |
| 'medusa-plugin-zalopay': ['0.0.40'], | |
| '@kvytech/medusa-plugin-newsletter': ['0.0.5'], | |
| '@posthog/databricks-plugin': ['0.0.8'], | |
| 'capacitor-voice-recorder-wav': ['6.0.3'], | |
| 'create-hardhat3-app': ['1.1.1', '1.1.2'], | |
| 'rollup-plugin-httpfile': ['0.2.1'], | |
| '@ensdomains/name-wrapper': ['1.0.1'], | |
| 'test-foundry-app': ['1.0.3'], | |
| 'jan-browser': ['0.13.1'], | |
| '@mparpaillon/page': ['1.0.1'], | |
| 'go-template': ['0.1.8'], | |
| '@strapbuild/react-native-perspective-image-cropper': ['0.4.15'], | |
| 'manual-billing-system-miniapp-api': ['1.3.1'], | |
| 'korea-administrative-area-geo-json-util': ['1.0.7'], | |
| '@posthog/currency-normalization-plugin': ['0.0.8'], | |
| '@posthog/web-dev-server': ['1.0.5'], | |
| '@posthog/pagerduty-plugin': ['0.0.8'], | |
| '@posthog/event-sequence-timer-plugin': ['0.0.8'], | |
| '@posthog/automatic-cohorts-plugin': ['0.0.8'], | |
| '@posthog/first-time-event-tracker': ['0.0.8'], | |
| '@actbase/css-to-react-native-transform': ['1.0.3'], | |
| '@posthog/url-normalizer-plugin': ['0.0.8'], | |
| '@posthog/twilio-plugin': ['0.0.8'], | |
| '@actbase/node-server': ['1.1.19'], | |
| '@posthog/gitub-star-sync-plugin': ['0.0.8'], | |
| '@seung-ju/react-native-action-sheet': ['0.2.1'], | |
| '@posthog/maxmind-plugin': ['0.1.6'], | |
| '@posthog/github-release-tracking-plugin': ['0.0.8'], | |
| '@actbase/react-native-fast-image': ['8.5.13'], | |
| '@posthog/customerio-plugin': ['0.0.8'], | |
| '@posthog/kinesis-plugin': ['0.0.8'], | |
| '@actbase/react-native-less-transformer': ['1.0.6'], | |
| '@posthog/taxonomy-plugin': ['0.0.8'], | |
| 'medusa-plugin-product-reviews-kvy': ['0.0.4'], | |
| '@aryanhussain/my-angular-lib': ['0.0.23'], | |
| 'dotnet-template': ['0.0.4'], | |
| 'capacitor-plugin-scgssigninwithgoogle': ['0.0.5'], | |
| 'capacitor-purchase-history': ['0.0.10'], | |
| '@posthog/plugin-unduplicates': ['0.0.8'], | |
| 'posthog-plugin-hello-world': ['1.0.1'], | |
| 'esbuild-plugin-httpfile': ['0.4.1'], | |
| '@ensdomains/blacklist': ['1.0.1'], | |
| '@ensdomains/renewal-widget': ['0.1.10'], | |
| '@ensdomains/hackathon-registrar': ['1.0.5'], | |
| '@ensdomains/ccip-read-router': ['0.0.7'], | |
| '@mcp-use/mcp-use': ['1.0.1'], | |
| 'test-hardhat-app': ['1.0.3'], | |
| 'zuper-cli': ['1.0.1'], | |
| 'skills-use': ['0.1.2'], | |
| 'typeorm-orbit': ['0.2.27'], | |
| 'orbit-nebula-editor': ['1.0.2'], | |
| '@trigo/atrix-elasticsearch': ['2.0.1'], | |
| '@trigo/atrix-soap': ['1.0.2'], | |
| 'eslint-config-zeallat-base': ['1.0.4'], | |
| 'iron-shield-miniapp': ['0.0.2'], | |
| 'shinhan-limit-scrap': ['1.0.3'], | |
| 'create-glee-app': ['0.2.3'], | |
| '@seung-ju/next': ['0.0.2'], | |
| '@actbase/react-native-tiktok': ['1.1.3'], | |
| 'discord-bot-server': ['0.1.2'], | |
| '@seung-ju/openapi-generator': ['0.0.4'], | |
| '@seung-ju/react-hooks': ['0.0.2'], | |
| '@actbase/react-native-naver-login': ['1.0.1'], | |
| '@kvytech/medusa-plugin-announcement': ['0.0.8'], | |
| '@kvytech/components': ['0.0.2'], | |
| '@kvytech/cli': ['0.0.7'], | |
| '@kvytech/medusa-plugin-management': ['0.0.5'], | |
| '@kvytech/medusa-plugin-product-reviews': ['0.0.9'], | |
| '@kvytech/web': ['0.0.2'], | |
| scgsffcreator: ['1.0.5'], | |
| 'vite-plugin-httpfile': ['0.2.1'], | |
| '@ensdomains/curvearithmetics': ['1.0.1'], | |
| '@ensdomains/reverse-records': ['1.0.1'], | |
| '@ensdomains/ccip-read-dns-gateway': ['0.1.1'], | |
| '@ensdomains/unicode-confusables': ['0.1.1'], | |
| '@ensdomains/durin-middleware': ['0.0.2'], | |
| '@ensdomains/ccip-read-worker-viem': ['0.0.4'], | |
| atrix: ['1.0.1'], | |
| '@caretive/caret-cli': ['0.0.2'], | |
| 'exact-ticker': ['0.3.5'], | |
| '@orbitgtbelgium/orbit-components': ['1.2.9'], | |
| 'react-library-setup': ['0.0.6'], | |
| '@orbitgtbelgium/mapbox-gl-draw-scale-rotate-mode': ['1.1.1'], | |
| 'orbit-nebula-draw-tools': ['1.0.10'], | |
| '@orbitgtbelgium/time-slider': ['1.0.187'], | |
| 'react-element-prompt-inspector': ['0.1.18'], | |
| '@trigo/pathfinder-ui-css': ['0.1.1'], | |
| 'eslint-config-trigo': ['22.0.2'], | |
| '@trigo/fsm': ['3.4.2'], | |
| '@trigo/atrix': ['7.0.1'], | |
| '@trigo/atrix-postgres': ['1.0.3'], | |
| 'trigo-react-app': ['4.1.2'], | |
| '@trigo/eslint-config-trigo': ['3.3.1'], | |
| '@trigo/bool-expressions': ['4.1.3'], | |
| '@trigo/trigo-hapijs': ['5.0.1'], | |
| '@trigo/node-soap': ['0.5.4'], | |
| '@trigo/jsdt': ['0.2.1'], | |
| 'bool-expressions': ['0.1.2'], | |
| '@trigo/atrix-redis': ['1.0.2'], | |
| '@trigo/atrix-acl': ['4.0.2'], | |
| '@trigo/atrix-orientdb': ['1.0.2'], | |
| '@trigo/atrix-mongoose': ['1.0.2'], | |
| 'atrix-mongoose': ['1.0.1'], | |
| 'redux-forge': ['2.5.3'], | |
| '@trigo/keycloak-api': ['1.3.1'], | |
| '@mparpaillon/connector-parse': ['1.0.1'], | |
| '@mparpaillon/imagesloaded': ['4.1.2'], | |
| '@alaan/s2s-auth': ['2.0.3'], | |
| }; | |
| /** | |
| * Normalize version string by removing leading '^', '~', or '=' characters | |
| */ | |
| function normalizeVersion(version) { | |
| return version.replace(/^[~^=]/, ''); | |
| } | |
| /** | |
| * Check if a version matches any of the vulnerable versions | |
| */ | |
| function isVulnerableVersion(packageName, version) { | |
| const vulnerableVersions = VULNERABLE_PACKAGES[packageName]; | |
| if (!vulnerableVersions) { | |
| debugLog(`Package ${packageName} not in vulnerable list`); | |
| return false; | |
| } | |
| const normalizedVersion = normalizeVersion(version); | |
| debugLog( | |
| `Checking ${packageName} version ${version} (normalized: ${normalizedVersion}) against vulnerable versions:`, | |
| vulnerableVersions | |
| ); | |
| const isVulnerable = vulnerableVersions.some((vulnVersion) => { | |
| // Exact match | |
| if (normalizedVersion === vulnVersion) { | |
| debugLog( | |
| ` β Exact match found: ${normalizedVersion} === ${vulnVersion}` | |
| ); | |
| return true; | |
| } | |
| // Handle version ranges (e.g., "1.2.3" matches "1.2.3" even if installed as "^1.2.3") | |
| const startsWithMatch = normalizedVersion.startsWith(vulnVersion); | |
| if (startsWithMatch) { | |
| debugLog( | |
| ` β Prefix match found: ${normalizedVersion} starts with ${vulnVersion}` | |
| ); | |
| } | |
| return startsWithMatch; | |
| }); | |
| if (isVulnerable) { | |
| debugLog(` β VULNERABLE: ${packageName}@${version}`); | |
| } else { | |
| debugLog(` β Safe: ${packageName}@${version}`); | |
| } | |
| return isVulnerable; | |
| } | |
| /** | |
| * Check for vulnerable packages in pnpm-lock.yaml | |
| */ | |
| function checkVulnerablePackagesInPnpmLock() { | |
| const lockFilePath = join(process.cwd(), 'pnpm-lock.yaml'); | |
| const packages = []; | |
| debugLog(`Reading pnpm lock file: ${lockFilePath}`); | |
| try { | |
| // Read file once (lock files are typically manageable in size) | |
| const fileContent = readFileSync(lockFilePath, 'utf-8'); | |
| const allLines = fileContent.split('\n'); | |
| debugLog( | |
| `Lock file read: ${allLines.length} lines, ${fileContent.length} bytes` | |
| ); | |
| debugLog( | |
| `Checking ${Object.keys(VULNERABLE_PACKAGES).length} vulnerable packages` | |
| ); | |
| // For each vulnerable package, search in the lock file | |
| for (const packageName of Object.keys(VULNERABLE_PACKAGES)) { | |
| debugLog(`\nSearching for package: ${packageName}`); | |
| // Escape special regex characters in package name | |
| const escapedName = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
| // Pattern 1: Search for package entry in dependencies section | |
| // Format: 'package-name': or "package-name": or package-name: | |
| const packagePattern = new RegExp(`^\\s+['"]?${escapedName}['"]?:\\s*$`); | |
| debugLog(` Pattern 1 (dependencies): ${packagePattern}`); | |
| // Pattern 2: Search in packages section | |
| // Format: /package-name/version: | |
| const packagePathPattern = new RegExp( | |
| `^\\s+/${escapedName}/([0-9]+\\.[0-9]+\\.[0-9]+[^:]*):` | |
| ); | |
| debugLog(` Pattern 2 (packages section): ${packagePathPattern}`); | |
| let found = false; | |
| for (let i = 0; i < allLines.length; i++) { | |
| // Check for package entry in dependencies section | |
| if (packagePattern.test(allLines[i])) { | |
| debugLog( | |
| ` Found package entry at line ${i + 1}: ${allLines[i].trim()}` | |
| ); | |
| // Look for version in the next few lines (usually 1-3 lines after) | |
| for (let j = i + 1; j < Math.min(i + 5, allLines.length); j++) { | |
| const versionMatch = allLines[j].match( | |
| /^\s+version:\s*([0-9]+\.[0-9]+\.[0-9]+[^(\s]*)/ | |
| ); | |
| if (versionMatch) { | |
| const version = versionMatch[1]; | |
| debugLog(` Found version at line ${j + 1}: ${version}`); | |
| packages.push({ | |
| name: packageName, | |
| version: version, | |
| path: packageName, | |
| }); | |
| found = true; | |
| break; // Found, move to next package | |
| } | |
| } | |
| if (found) break; // Found package entry, move to next package | |
| } | |
| // Check for package in packages section | |
| const pathMatch = allLines[i].match(packagePathPattern); | |
| if (pathMatch) { | |
| const version = pathMatch[1]; | |
| debugLog( | |
| ` Found package in packages section at line ${i + 1}: version ${version}` | |
| ); | |
| packages.push({ | |
| name: packageName, | |
| version: version, | |
| path: packageName, | |
| }); | |
| found = true; | |
| break; // Found, move to next package | |
| } | |
| } | |
| if (!found) { | |
| debugLog(` Package ${packageName} not found in lock file`); | |
| } | |
| } | |
| debugLog(`\nTotal packages found in lock file: ${packages.length}`); | |
| return packages; | |
| } catch (error) { | |
| console.error('Error reading pnpm-lock.yaml:', error.message); | |
| debugLog('Error details:', error); | |
| return []; | |
| } | |
| } | |
| /** | |
| * Check for vulnerable packages in yarn.lock | |
| */ | |
| function checkVulnerablePackagesInYarnLock() { | |
| const lockFilePath = join(process.cwd(), 'yarn.lock'); | |
| const packages = []; | |
| debugLog(`Reading yarn lock file: ${lockFilePath}`); | |
| try { | |
| const fileContent = readFileSync(lockFilePath, 'utf-8'); | |
| const allLines = fileContent.split('\n'); | |
| debugLog( | |
| `Yarn lock file read: ${allLines.length} lines, ${fileContent.length} bytes` | |
| ); | |
| debugLog( | |
| `Checking ${Object.keys(VULNERABLE_PACKAGES).length} vulnerable packages` | |
| ); | |
| // Yarn lock format: | |
| // package-name@version: or "package-name@version": or "@scope/package-name@version": | |
| // version "x.y.z" | |
| for (const packageName of Object.keys(VULNERABLE_PACKAGES)) { | |
| debugLog(`\nSearching for package: ${packageName}`); | |
| // Escape special regex characters | |
| const escapedName = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
| // Pattern 1: package-name@version: (for unscoped packages) | |
| // Pattern 2: "@scope/package-name@version": (for scoped packages) | |
| const packageHeaderPattern = new RegExp( | |
| `^["']?${escapedName}@([0-9]+\\.[0-9]+\\.[0-9]+[^"':\\s]*):` | |
| ); | |
| debugLog(` Header pattern: ${packageHeaderPattern}`); | |
| let found = false; | |
| for (let i = 0; i < allLines.length; i++) { | |
| // Check for package header line | |
| const headerMatch = allLines[i].match(packageHeaderPattern); | |
| if (headerMatch) { | |
| debugLog( | |
| ` Found package header at line ${i + 1}: ${allLines[i].trim()}` | |
| ); | |
| // Look for version in the next few lines (usually 1-2 lines after) | |
| for (let j = i + 1; j < Math.min(i + 10, allLines.length); j++) { | |
| // Yarn format: version "x.y.z" | |
| const versionMatch = allLines[j].match( | |
| /^\s+version\s+"([0-9]+\.[0-9]+\.[0-9]+[^"]*)"/ | |
| ); | |
| if (versionMatch) { | |
| const version = versionMatch[1]; | |
| debugLog(` Found version at line ${j + 1}: ${version}`); | |
| packages.push({ | |
| name: packageName, | |
| version: version, | |
| path: packageName, | |
| }); | |
| found = true; | |
| break; | |
| } | |
| // Stop if we hit another package entry (starts with package name or empty line followed by package) | |
| if (allLines[j].match(/^[^#\s]/) && !allLines[j].match(/^\s/)) { | |
| break; | |
| } | |
| } | |
| if (found) break; | |
| } | |
| } | |
| if (!found) { | |
| debugLog(` Package ${packageName} not found in yarn.lock`); | |
| } | |
| } | |
| debugLog(`\nTotal packages found in yarn.lock: ${packages.length}`); | |
| return packages; | |
| } catch (error) { | |
| console.error('Error reading yarn.lock:', error.message); | |
| debugLog('Error details:', error); | |
| return []; | |
| } | |
| } | |
| /** | |
| * Check for vulnerable packages in package-lock.json | |
| */ | |
| function checkVulnerablePackagesInNpmLock() { | |
| const lockFilePath = join(process.cwd(), 'package-lock.json'); | |
| const packages = []; | |
| debugLog(`Reading npm lock file: ${lockFilePath}`); | |
| try { | |
| const fileContent = readFileSync(lockFilePath, 'utf-8'); | |
| const lockData = JSON.parse(fileContent); | |
| debugLog(`NPM lock file parsed successfully`); | |
| debugLog( | |
| `Checking ${Object.keys(VULNERABLE_PACKAGES).length} vulnerable packages` | |
| ); | |
| // NPM lock format (v2+): packages["node_modules/package-name"] | |
| // NPM lock format (v1): dependencies["package-name"] | |
| const packagesObj = lockData.packages || lockData.dependencies || {}; | |
| debugLog(`Found ${Object.keys(packagesObj).length} packages in lock file`); | |
| debugLog(`Lock file version: ${lockData.lockfileVersion || '1'}`); | |
| for (const packageName of Object.keys(VULNERABLE_PACKAGES)) { | |
| debugLog(`\nSearching for package: ${packageName}`); | |
| // Build possible paths for package-lock.json | |
| const possiblePaths = []; | |
| // For scoped packages like @scope/package | |
| if (packageName.startsWith('@')) { | |
| const [scope, name] = packageName.split('/'); | |
| possiblePaths.push( | |
| `node_modules/${packageName}`, | |
| packageName, | |
| `node_modules/${scope}/node_modules/${name}` | |
| ); | |
| } else { | |
| // For unscoped packages | |
| possiblePaths.push(`node_modules/${packageName}`, packageName); | |
| } | |
| let found = false; | |
| for (const path of possiblePaths) { | |
| const pkg = packagesObj[path]; | |
| if (pkg && pkg.version) { | |
| const version = pkg.version; | |
| debugLog(` Found at path "${path}": version ${version}`); | |
| packages.push({ | |
| name: packageName, | |
| version: version, | |
| path: packageName, | |
| }); | |
| found = true; | |
| break; | |
| } | |
| } | |
| // Also check nested dependencies (for older lock file versions) | |
| if (!found && lockData.dependencies) { | |
| function searchDependencies(deps) { | |
| for (const [name, pkg] of Object.entries(deps)) { | |
| // Check if this matches our package name (handle scoped packages) | |
| const matches = | |
| name === packageName || | |
| name === packageName.split('/').pop() || | |
| (packageName.startsWith('@') && name.startsWith(packageName)); | |
| if (matches && pkg.version) { | |
| debugLog( | |
| ` Found in dependencies at "${name}": version ${pkg.version}` | |
| ); | |
| packages.push({ | |
| name: packageName, | |
| version: pkg.version, | |
| path: packageName, | |
| }); | |
| return true; | |
| } | |
| // Recursively search nested dependencies | |
| if (pkg.dependencies && searchDependencies(pkg.dependencies)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| if (searchDependencies(lockData.dependencies)) { | |
| found = true; | |
| } | |
| } | |
| if (!found) { | |
| debugLog(` Package ${packageName} not found in package-lock.json`); | |
| } | |
| } | |
| debugLog(`\nTotal packages found in package-lock.json: ${packages.length}`); | |
| return packages; | |
| } catch (error) { | |
| console.error('Error reading package-lock.json:', error.message); | |
| debugLog('Error details:', error); | |
| return []; | |
| } | |
| } | |
| /** | |
| * Get installed packages by checking lock file for vulnerable packages only | |
| */ | |
| function getInstalledPackages() { | |
| debugLog('Getting installed packages...'); | |
| const packageManager = detectPackageManager(); | |
| let lockFilePackages = []; | |
| // Check appropriate lock file based on package manager | |
| if (packageManager === 'pnpm') { | |
| lockFilePackages = checkVulnerablePackagesInPnpmLock(); | |
| } else if (packageManager === 'yarn') { | |
| lockFilePackages = checkVulnerablePackagesInYarnLock(); | |
| } else if (packageManager === 'npm') { | |
| lockFilePackages = checkVulnerablePackagesInNpmLock(); | |
| } | |
| debugLog(`Found ${lockFilePackages.length} packages in lock file`); | |
| if (lockFilePackages.length > 0) { | |
| debugLog('Using packages from lock file'); | |
| return lockFilePackages; | |
| } | |
| // Fallback: check package.json (only direct dependencies) | |
| debugLog('No packages found in lock file, falling back to package.json'); | |
| const packageJsonPackages = getPackagesFromPackageJson(); | |
| debugLog(`Found ${packageJsonPackages.length} packages in package.json`); | |
| return packageJsonPackages; | |
| } | |
| /** | |
| * Fallback: Get packages from root package.json | |
| */ | |
| function getPackagesFromPackageJson() { | |
| try { | |
| const packageJsonPath = join(process.cwd(), 'package.json'); | |
| debugLog(`Reading package.json: ${packageJsonPath}`); | |
| const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); | |
| const packages = []; | |
| if (packageJson.dependencies) { | |
| const depCount = Object.keys(packageJson.dependencies).length; | |
| debugLog(`Found ${depCount} dependencies`); | |
| Object.entries(packageJson.dependencies).forEach(([name, version]) => { | |
| debugLog(` ${name}: ${version}`); | |
| packages.push({ | |
| name, | |
| version: version, | |
| path: name, | |
| }); | |
| }); | |
| } | |
| if (packageJson.devDependencies) { | |
| const devDepCount = Object.keys(packageJson.devDependencies).length; | |
| debugLog(`Found ${devDepCount} devDependencies`); | |
| Object.entries(packageJson.devDependencies).forEach(([name, version]) => { | |
| debugLog(` ${name}: ${version}`); | |
| packages.push({ | |
| name, | |
| version: version, | |
| path: name, | |
| }); | |
| }); | |
| } | |
| return packages; | |
| } catch (error) { | |
| console.error('Error reading package.json:', error); | |
| debugLog('Error details:', error); | |
| return []; | |
| } | |
| } | |
| /** | |
| * Main function | |
| */ | |
| function main() { | |
| if (DEBUG) { | |
| console.log('π DEBUG MODE ENABLED\n'); | |
| } | |
| const packageManager = detectPackageManager(); | |
| const pmDisplay = packageManager ? ` (${packageManager})` : ''; | |
| console.log('π Checking for SHA1Hulud vulnerability...\n'); | |
| console.log( | |
| 'Reference: https://helixguard.ai/blog/malicious-sha1hulud-2025-11-24' | |
| ); | |
| if (packageManager) { | |
| console.log(`Package manager: ${packageManager}\n`); | |
| } else { | |
| console.log( | |
| 'Package manager: auto-detected (using package.json fallback)\n' | |
| ); | |
| } | |
| debugLog( | |
| `Vulnerable packages to check: ${Object.keys(VULNERABLE_PACKAGES).length}` | |
| ); | |
| debugLog( | |
| `Vulnerable packages list:`, | |
| Object.keys(VULNERABLE_PACKAGES).slice(0, 10), | |
| '...' | |
| ); | |
| const installedPackages = getInstalledPackages(); | |
| debugLog( | |
| `\nChecking ${installedPackages.length} installed packages for vulnerabilities` | |
| ); | |
| const vulnerablePackages = []; | |
| for (const pkg of installedPackages) { | |
| debugLog(`\nChecking: ${pkg.name}@${pkg.version}`); | |
| if (isVulnerableVersion(pkg.name, pkg.version)) { | |
| debugLog(` β Added to vulnerable list`); | |
| vulnerablePackages.push(pkg); | |
| } | |
| } | |
| debugLog(`\n\n=== SUMMARY ===`); | |
| debugLog(`Total installed packages checked: ${installedPackages.length}`); | |
| debugLog(`Vulnerable packages found: ${vulnerablePackages.length}`); | |
| if (vulnerablePackages.length === 0) { | |
| console.log( | |
| 'β No vulnerable packages found. Your workspace appears to be safe.\n' | |
| ); | |
| process.exit(0); | |
| } else { | |
| console.log( | |
| `β Found ${vulnerablePackages.length} vulnerable package(s):\n` | |
| ); | |
| vulnerablePackages.forEach((pkg) => { | |
| const vulnerableVersions = VULNERABLE_PACKAGES[pkg.name]; | |
| console.log(` π¦ ${pkg.name}`); | |
| console.log(` Installed version: ${pkg.version}`); | |
| console.log(` Vulnerable versions: ${vulnerableVersions.join(', ')}`); | |
| if (pkg.path && pkg.path !== pkg.name) { | |
| console.log(` Dependency path: ${pkg.path}`); | |
| } | |
| console.log(); | |
| }); | |
| console.log('\nβ οΈ ACTION REQUIRED:'); | |
| console.log( | |
| ' Please update the vulnerable packages to non-vulnerable versions.' | |
| ); | |
| console.log( | |
| ' For more information, visit: https://helixguard.ai/blog/malicious-sha1hulud-2025-11-24\n' | |
| ); | |
| process.exit(1); | |
| } | |
| } | |
| main(); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To run the script:
curl -s https://gist.githubusercontent.com/Lp-Francois/9e70eb7488bcc8ec76e0e27ed1c738e5/raw/ca9b7e31797fb1421938aaead54ad2381572890f/check-sha1hulud-vuln.js | node