Last active
November 24, 2025 16:17
-
-
Save christian-taillon/286f7cacd758cad19b3ac93e1629996f to your computer and use it in GitHub Desktop.
Check for compromised packages
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
| #!/bin/bash | |
| # Shai Hulud NPM Package Scanner | |
| # Scans directories for compromised packages and versions | |
| # Usage: ./check_shai_hulud_packages.sh [directory] | |
| # If no directory specified, scans current directory | |
| # Reference Compromised Lists: https://www.aikido.dev/blog/shai-hulud-strikes-again-hitting-zapier-ensdomains | |
| # Color codes for output | |
| RED='\033[0;31m' | |
| YELLOW='\033[1;33m' | |
| GREEN='\033[0;32m' | |
| NC='\033[0m' # No Color | |
| # Compromised packages with malicious versions | |
| declare -A COMPROMISED_PACKAGES=( | |
| ["@zapier/zapier-sdk"]="0.15.5 0.15.6 0.15.7" | |
| ["zapier-platform-core"]="18.0.2 18.0.3 18.0.4" | |
| ["zapier-platform-cli"]="18.0.2 18.0.3 18.0.4" | |
| ["zapier-platform-schema"]="18.0.2 18.0.3 18.0.4" | |
| ["@zapier/mcp-integration"]="3.0.1 3.0.2 3.0.3" | |
| ["@zapier/secret-scrubber"]="1.1.3 1.1.4 1.1.5" | |
| ["@zapier/ai-actions-react"]="0.1.12 0.1.13 0.1.14" | |
| ["@zapier/stubtree"]="0.1.2 0.1.3 0.1.4" | |
| ["@zapier/babel-preset-zapier"]="ANY" | |
| ["zapier-scripts"]="7.8.3 7.8.4" | |
| ["zapier-platform-legacy-scripting-runner"]="ANY" | |
| ["@ensdomains/ens-validation"]="0.1.1" | |
| ["@ensdomains/content-hash"]="3.0.1" | |
| ["ethereum-ens"]="0.8.1" | |
| ["@ensdomains/react-ens-address"]="0.0.32" | |
| ["@ensdomains/ens-contracts"]="1.6.1" | |
| ["@ensdomains/ensjs"]="4.0.3" | |
| ["@ensdomains/ens-archived-contracts"]="0.0.3" | |
| ["@ensdomains/dnssecoraclejs"]="0.2.9" | |
| ["@ensdomains/address-encoder"]="0.1.5" | |
| ["typeorm-orbit"]="ANY" | |
| ["orbit-nebula-draw-tools"]="ANY" | |
| ["@trigo/atrix-postgres"]="ANY" | |
| ["@orbitgtbelgium/orbit-components"]="ANY" | |
| ["@orbitgtbelgium/time-slider"]="ANY" | |
| ["@orbitgtbelgium/mapbox-gl-draw-cut-polygon-mode"]="ANY" | |
| ["command-irail"]="ANY" | |
| ["@trigo/fsm"]="ANY" | |
| ["@trigo/trigo-hapijs"]="ANY" | |
| ["trigo-react-app"]="ANY" | |
| ["react-element-prompt-inspector"]="ANY" | |
| ["bool-expressions"]="ANY" | |
| ["atrix-mongoose"]="ANY" | |
| ["orbit-nebula-editor"]="ANY" | |
| ["orbit-boxicons"]="ANY" | |
| ["@trigo/atrix"]="ANY" | |
| ["redux-forge"]="ANY" | |
| ["atrix"]="ANY" | |
| ["@trigo/atrix-acl"]="ANY" | |
| ["crypto-addr-codec"]="ANY" | |
| ["@trigo/atrix-swagger"]="ANY" | |
| ["@trigo/atrix-soap"]="ANY" | |
| ["@trigo/keycloak-api"]="ANY" | |
| ["@trigo/atrix-elasticsearch"]="ANY" | |
| ["@trigo/hapi-auth-signedlink"]="ANY" | |
| ["@trigo/atrix-pubsub"]="ANY" | |
| ["@trigo/bool-expressions"]="ANY" | |
| ["@trigo/atrix-orientdb"]="ANY" | |
| ["@trigo/node-soap"]="ANY" | |
| ["eslint-config-trigo"]="ANY" | |
| ["@trigo/atrix-redis"]="ANY" | |
| ["@trigo/eslint-config-trigo"]="ANY" | |
| ["@trigo/jsdt"]="ANY" | |
| ["@trigo/pathfinder-ui-css"]="ANY" | |
| ["@louisle2/cortex-js"]="ANY" | |
| ["@mparpaillon/imagesloaded"]="ANY" | |
| ["@mparpaillon/connector-parse"]="ANY" | |
| ["react-component-taggers"]="ANY" | |
| ["token.js-fork"]="ANY" | |
| ["@orbitgtbelgium/mapbox-gl-draw-scale-rotate-mode"]="ANY" | |
| ["orbit-soap"]="ANY" | |
| ["react-library-setup"]="ANY" | |
| ["exact-ticker"]="ANY" | |
| ["jan-browser"]="ANY" | |
| ["@louisle2/core"]="ANY" | |
| ["lite-serper-mcp-server"]="ANY" | |
| ["cpu-instructions"]="ANY" | |
| ["evm-checkcode-cli"]="ANY" | |
| ["bytecode-checker-cli"]="ANY" | |
| ["gate-evm-check-code2"]="ANY" | |
| ["devstart-cli"]="ANY" | |
| ) | |
| # Set search directory | |
| SEARCH_DIR="${1:-.}" | |
| echo -e "${GREEN}=== Shai Hulud Package Scanner ===${NC}" | |
| echo "Scanning: $SEARCH_DIR" | |
| echo "" | |
| # Check if jq is available for better JSON parsing | |
| HAS_JQ=false | |
| if command -v jq &> /dev/null; then | |
| HAS_JQ=true | |
| fi | |
| # Counter for findings | |
| FINDINGS_COUNT=0 | |
| PROJECTS_SCANNED=0 | |
| # Find all package.json files | |
| while IFS= read -r package_file; do | |
| ((PROJECTS_SCANNED++)) | |
| project_dir=$(dirname "$package_file") | |
| # Check dependencies and devDependencies | |
| for pkg_name in "${!COMPROMISED_PACKAGES[@]}"; do | |
| malicious_versions="${COMPROMISED_PACKAGES[$pkg_name]}" | |
| if $HAS_JQ; then | |
| # Use jq for robust JSON parsing | |
| installed_version=$(jq -r ".dependencies[\"$pkg_name\"] // .devDependencies[\"$pkg_name\"] // empty" "$package_file" 2>/dev/null) | |
| else | |
| # Fallback to grep (less reliable but works without jq) | |
| installed_version=$(grep -A 1 "\"$pkg_name\"" "$package_file" 2>/dev/null | grep -oP '"\K[^"]+' | tail -1) | |
| fi | |
| if [ -n "$installed_version" ]; then | |
| # Remove version prefixes like ^, ~, >=, etc. | |
| clean_version=$(echo "$installed_version" | sed 's/[\^~>=<]//g') | |
| # Check if version matches malicious versions | |
| if [ "$malicious_versions" = "ANY" ]; then | |
| echo -e "${RED}[CRITICAL]${NC} Found: ${YELLOW}$pkg_name${NC} @ $installed_version" | |
| echo " Location: $package_file" | |
| echo -e " ${YELLOW}⚠ No version info provided by source - package may be compromised${NC}" | |
| echo " Action: Verify package integrity or remove until confirmed safe" | |
| echo "" | |
| ((FINDINGS_COUNT++)) | |
| else | |
| for bad_version in $malicious_versions; do | |
| if [ "$clean_version" = "$bad_version" ]; then | |
| echo -e "${RED}[CRITICAL]${NC} Found: ${YELLOW}$pkg_name${NC} @ $installed_version" | |
| echo " Location: $package_file" | |
| echo " Malicious versions: $malicious_versions" | |
| echo "" | |
| ((FINDINGS_COUNT++)) | |
| break | |
| fi | |
| done | |
| fi | |
| fi | |
| done | |
| # Also check package-lock.json if it exists | |
| lock_file="${project_dir}/package-lock.json" | |
| if [ -f "$lock_file" ]; then | |
| for pkg_name in "${!COMPROMISED_PACKAGES[@]}"; do | |
| malicious_versions="${COMPROMISED_PACKAGES[$pkg_name]}" | |
| if $HAS_JQ; then | |
| # Check both packages and dependencies sections in lockfile | |
| installed_version=$(jq -r ".packages[\"node_modules/$pkg_name\"].version // .dependencies[\"$pkg_name\"].version // empty" "$lock_file" 2>/dev/null) | |
| else | |
| installed_version=$(grep -A 2 "\"$pkg_name\"" "$lock_file" 2>/dev/null | grep "\"version\"" | head -1 | grep -oP '"\K[0-9.]+') | |
| fi | |
| if [ -n "$installed_version" ]; then | |
| if [ "$malicious_versions" = "ANY" ]; then | |
| echo -e "${RED}[LOCKFILE]${NC} Found in lock: ${YELLOW}$pkg_name${NC} @ $installed_version" | |
| echo " Location: $lock_file" | |
| echo -e " ${YELLOW}⚠ No version info provided by source - may be compromised${NC}" | |
| echo "" | |
| ((FINDINGS_COUNT++)) | |
| else | |
| for bad_version in $malicious_versions; do | |
| if [ "$installed_version" = "$bad_version" ]; then | |
| echo -e "${RED}[LOCKFILE]${NC} Found in lock: ${YELLOW}$pkg_name${NC} @ $installed_version" | |
| echo " Location: $lock_file" | |
| echo " Malicious versions: $malicious_versions" | |
| echo "" | |
| ((FINDINGS_COUNT++)) | |
| break | |
| fi | |
| done | |
| fi | |
| fi | |
| done | |
| fi | |
| done < <(find "$SEARCH_DIR" -name "package.json" -not -path "*/node_modules/*" 2>/dev/null) | |
| echo -e "${GREEN}=== Scan Complete ===${NC}" | |
| echo "Projects scanned: $PROJECTS_SCANNED" | |
| echo "Compromised packages found: $FINDINGS_COUNT" | |
| echo "" | |
| echo -e "${YELLOW}Note on findings:${NC}" | |
| echo "• Packages with specific versions = Confirmed malicious by Aikido Security" | |
| echo "• Packages marked 'No version info' = Listed as compromised but specific versions unknown" | |
| echo "" | |
| if [ $FINDINGS_COUNT -gt 0 ]; then | |
| echo -e "${RED}⚠️ IMMEDIATE ACTION REQUIRED ⚠️${NC}" | |
| echo "1. Remove or downgrade affected packages" | |
| echo "2. Rotate all API keys, tokens, and credentials" | |
| echo "3. Check for home directory file deletion" | |
| echo "4. Review git history for unauthorized commits" | |
| else | |
| echo -e "${GREEN}✓ No compromised packages detected${NC}" | |
| fi | |
| if ! $HAS_JQ; then | |
| echo "" | |
| echo -e "${YELLOW}Note: Install 'jq' for more accurate scanning${NC}" | |
| echo " sudo apt install jq # Debian/Ubuntu" | |
| echo " sudo dnf install jq # Fedora" | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment