Skip to content

Instantly share code, notes, and snippets.

@christian-taillon
Last active November 24, 2025 16:17
Show Gist options
  • Select an option

  • Save christian-taillon/286f7cacd758cad19b3ac93e1629996f to your computer and use it in GitHub Desktop.

Select an option

Save christian-taillon/286f7cacd758cad19b3ac93e1629996f to your computer and use it in GitHub Desktop.
Check for compromised packages
#!/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