Skip to content

Instantly share code, notes, and snippets.

@stepanogil
Last active November 26, 2025 13:06
Show Gist options
  • Select an option

  • Save stepanogil/f6cb9c6c73ef9e1becd73488e20ed002 to your computer and use it in GitHub Desktop.

Select an option

Save stepanogil/f6cb9c6c73ef9e1becd73488e20ed002 to your computer and use it in GitHub Desktop.
Shai Hulud Detection Script
#!/bin/bash
#################################################################
# Shai-Hulud 2.0 Detection Script
# Checks for infection indicators and compromised packages
# Downloads latest IoC list from Wiz Security Research
#################################################################
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
INFECTED=0
IOC_URL="https://raw.githubusercontent.com/wiz-sec-public/wiz-research-iocs/main/reports/shai-hulud-2-packages.csv"
IOC_FILE="/tmp/shai-hulud-2-packages.csv"
echo -e "${BLUE}================================================================${NC}"
echo -e "${BLUE} Shai-Hulud 2.0 Detection Script${NC}"
echo -e "${BLUE}================================================================${NC}"
echo ""
# Function to print findings
print_finding() {
local severity=$1
local message=$2
if [ "$severity" == "CRITICAL" ]; then
echo -e "${RED}[CRITICAL] $message${NC}"
INFECTED=1
elif [ "$severity" == "WARNING" ]; then
echo -e "${YELLOW}[WARNING] $message${NC}"
elif [ "$severity" == "INFO" ]; then
echo -e "${GREEN}[INFO] $message${NC}"
fi
}
# Download latest IoC list
echo -e "${BLUE}[*] Downloading latest compromised package list from Wiz Security...${NC}"
if command -v curl &> /dev/null; then
curl -s "$IOC_URL" -o "$IOC_FILE" 2>/dev/null
if [ $? -eq 0 ] && [ -s "$IOC_FILE" ]; then
print_finding "INFO" "Successfully downloaded IoC list ($(wc -l < "$IOC_FILE") packages)"
else
print_finding "WARNING" "Failed to download IoC list, will use built-in package list"
IOC_FILE=""
fi
elif command -v wget &> /dev/null; then
wget -q "$IOC_URL" -O "$IOC_FILE" 2>/dev/null
if [ $? -eq 0 ] && [ -s "$IOC_FILE" ]; then
print_finding "INFO" "Successfully downloaded IoC list ($(wc -l < "$IOC_FILE") packages)"
else
print_finding "WARNING" "Failed to download IoC list, will use built-in package list"
IOC_FILE=""
fi
else
print_finding "WARNING" "curl/wget not found, will use built-in package list"
IOC_FILE=""
fi
echo ""
#################################################################
# CHECK 1: Malicious payload files
#################################################################
echo -e "${BLUE}[1] Checking for malicious payload files...${NC}"
PAYLOAD_FILES=$(find . -type f \( \
-name "setup_bun.js" -o \
-name "bun_environment.js" -o \
-name "cloud.json" -o \
-name "contents.json" -o \
-name "environment.json" -o \
-name "truffleSecrets.json" -o \
-name "actionsSecrets.json" \
\) 2>/dev/null)
if [ -n "$PAYLOAD_FILES" ]; then
print_finding "CRITICAL" "Found malicious payload files:"
echo "$PAYLOAD_FILES"
else
print_finding "INFO" "No malicious payload files found"
fi
echo ""
#################################################################
# CHECK 2: Suspicious large JavaScript files (payload is 10MB+)
#################################################################
echo -e "${BLUE}[2] Checking for suspiciously large JavaScript files (>5MB)...${NC}"
LARGE_JS=$(find . -name "*.js" -size +5M -exec ls -lh {} \; 2>/dev/null)
if [ -n "$LARGE_JS" ]; then
print_finding "WARNING" "Found large JavaScript files (bun_environment.js is ~10MB):"
echo "$LARGE_JS"
else
print_finding "INFO" "No suspiciously large JS files found"
fi
echo ""
#################################################################
# CHECK 3: Malicious GitHub workflows
#################################################################
echo -e "${BLUE}[3] Checking for malicious GitHub workflows...${NC}"
if [ -d ".github/workflows" ]; then
# Check for discussion.yaml
if [ -f ".github/workflows/discussion.yaml" ]; then
print_finding "CRITICAL" "Found backdoor workflow: .github/workflows/discussion.yaml"
fi
# Check for formatter_* workflows
FORMATTER_WORKFLOWS=$(find .github/workflows -name "formatter_*.yml" -o -name "formatter_*.yaml" 2>/dev/null)
if [ -n "$FORMATTER_WORKFLOWS" ]; then
print_finding "CRITICAL" "Found exfiltration workflows:"
echo "$FORMATTER_WORKFLOWS"
fi
# Check for self-hosted runner usage
SELF_HOSTED=$(grep -r "runs-on: self-hosted" .github/workflows/ 2>/dev/null)
if [ -n "$SELF_HOSTED" ]; then
print_finding "WARNING" "Found self-hosted runner references:"
echo "$SELF_HOSTED"
fi
# Check for secret exfiltration patterns
SECRET_EXFIL=$(grep -r "toJSON(secrets)" .github/workflows/ 2>/dev/null)
if [ -n "$SECRET_EXFIL" ]; then
print_finding "CRITICAL" "Found secret exfiltration pattern:"
echo "$SECRET_EXFIL"
fi
# Check for SHA1HULUD or RUNNER_TRACKING_ID
HULUD_REF=$(grep -r "SHA1HULUD\|RUNNER_TRACKING_ID" .github/ 2>/dev/null)
if [ -n "$HULUD_REF" ]; then
print_finding "CRITICAL" "Found Shai-Hulud references in workflows:"
echo "$HULUD_REF"
fi
if [ -z "$FORMATTER_WORKFLOWS" ] && [ -z "$SELF_HOSTED" ] && [ -z "$SECRET_EXFIL" ] && [ -z "$HULUD_REF" ] && [ ! -f ".github/workflows/discussion.yaml" ]; then
print_finding "INFO" "No malicious workflows detected"
fi
else
print_finding "INFO" "No .github/workflows directory found"
fi
echo ""
#################################################################
# CHECK 4: Preinstall scripts in package.json
#################################################################
echo -e "${BLUE}[4] Checking package.json files for preinstall scripts...${NC}"
PREINSTALL_SCRIPTS=$(find . -name "package.json" -exec sh -c '
if grep -q "preinstall" "$1" 2>/dev/null; then
echo "$1"
fi
' _ {} \; 2>/dev/null)
if [ -n "$PREINSTALL_SCRIPTS" ]; then
print_finding "WARNING" "Found package.json files with preinstall scripts:"
echo "$PREINSTALL_SCRIPTS"
echo ""
echo "Reviewing preinstall content:"
for pkg in $PREINSTALL_SCRIPTS; do
echo "--- $pkg ---"
grep -A 3 "preinstall" "$pkg" 2>/dev/null
done
else
print_finding "INFO" "No preinstall scripts found in package.json files"
fi
echo ""
#################################################################
# CHECK 5: Compromised package versions
#################################################################
echo -e "${BLUE}[5] Checking for compromised package versions...${NC}"
check_package_file() {
local lockfile=$1
local found_packages=""
if [ ! -f "$lockfile" ]; then
return
fi
if [ -n "$IOC_FILE" ] && [ -s "$IOC_FILE" ]; then
# Use downloaded IoC list
while IFS=, read -r package version; do
# Skip header and empty lines
if [ "$package" == "package" ] || [ -z "$package" ]; then
continue
fi
# Clean up package and version (remove quotes and whitespace)
package=$(echo "$package" | tr -d '"' | xargs)
version=$(echo "$version" | tr -d '"' | xargs)
if grep -q "$package.*$version" "$lockfile" 2>/dev/null; then
found_packages="${found_packages}\n - $package@$version"
fi
done < "$IOC_FILE"
else
print_finding "WARNING" "No IoC list available, skipping package version check for $lockfile"
fi
if [ -n "$found_packages" ]; then
print_finding "CRITICAL" "Found compromised packages in $lockfile:"
echo -e "$found_packages"
fi
}
# Check all lock files
for lockfile in package-lock.json yarn.lock pnpm-lock.yaml; do
if [ -f "$lockfile" ]; then
check_package_file "$lockfile"
fi
done
# Also check node_modules if present
if [ -d "node_modules" ]; then
echo ""
echo "Scanning node_modules directory..."
SUSPICIOUS_MODULES=$(find node_modules -type f \( -name "setup_bun.js" -o -name "bun_environment.js" \) 2>/dev/null)
if [ -n "$SUSPICIOUS_MODULES" ]; then
print_finding "CRITICAL" "Found malicious files in node_modules:"
echo "$SUSPICIOUS_MODULES"
fi
fi
if [ ! -f "package-lock.json" ] && [ ! -f "yarn.lock" ] && [ ! -f "pnpm-lock.yaml" ]; then
print_finding "INFO" "No lock files found in current directory"
fi
echo ""
#################################################################
# CHECK 6: Git history for unauthorized commits
#################################################################
echo -e "${BLUE}[6] Checking git history for suspicious commits...${NC}"
if [ -d ".git" ]; then
HULUD_COMMITS=$(git log --all --oneline --grep="discussion\|formatter\|hulud\|SHA1" 2>/dev/null)
if [ -n "$HULUD_COMMITS" ]; then
print_finding "WARNING" "Found potentially suspicious commits:"
echo "$HULUD_COMMITS"
else
print_finding "INFO" "No suspicious commits found in git history"
fi
else
print_finding "INFO" "Not a git repository"
fi
echo ""
#################################################################
# CHECK 7: DNS hijacking indicators
#################################################################
echo -e "${BLUE}[7] Checking for DNS hijacking indicators...${NC}"
if [ -f "/etc/hosts" ]; then
SUSPICIOUS_HOSTS=$(grep -E "webhook\.site|suspicious" /etc/hosts 2>/dev/null)
if [ -n "$SUSPICIOUS_HOSTS" ]; then
print_finding "WARNING" "Found suspicious entries in /etc/hosts:"
echo "$SUSPICIOUS_HOSTS"
else
print_finding "INFO" "No suspicious DNS entries found"
fi
else
print_finding "INFO" "Cannot check /etc/hosts (not accessible)"
fi
echo ""
#################################################################
# CHECK 8: Home directory destruction indicators
#################################################################
echo -e "${BLUE}[8] Checking for signs of destructive payload...${NC}"
# Check for recently deleted files (if available)
if command -v journalctl &> /dev/null; then
DELETION_LOGS=$(journalctl -S today | grep -i "delete\|remove\|rm -rf" 2>/dev/null | grep -i "home" | head -10)
if [ -n "$DELETION_LOGS" ]; then
print_finding "WARNING" "Found recent deletion activity in system logs"
fi
fi
print_finding "INFO" "Manual review of recent file deletions recommended"
echo ""
#################################################################
# SUMMARY
#################################################################
echo -e "${BLUE}================================================================${NC}"
echo -e "${BLUE} SUMMARY${NC}"
echo -e "${BLUE}================================================================${NC}"
if [ $INFECTED -eq 1 ]; then
echo -e "${RED}⚠️ CRITICAL: INFECTION DETECTED${NC}"
echo ""
echo "IMMEDIATE ACTIONS REQUIRED:"
echo "1. Disconnect from network immediately"
echo "2. DO NOT run 'npm install' or any package manager commands"
echo "3. Rotate ALL credentials (GitHub PATs, npm tokens, cloud keys, SSH keys)"
echo "4. Check GitHub for unauthorized repositories (search for 'Sha1-Hulud: The Second Coming')"
echo "5. Review GitHub Actions runners for 'SHA1HULUD' runner"
echo "6. Clear npm cache: npm cache clean --force"
echo "7. Delete node_modules: rm -rf node_modules"
echo "8. Restore from clean backup (pre-November 21, 2025)"
echo "9. Scan all systems that ran npm install recently"
echo "10. Report to your security team immediately"
else
echo -e "${GREEN}✓ No definitive infection indicators found${NC}"
echo ""
echo "RECOMMENDED PRECAUTIONS:"
echo "1. Review package.json for any unexpected dependencies"
echo "2. Check GitHub account for any new repositories"
echo "3. Verify no unexpected GitHub Actions runners registered"
echo "4. Monitor for unusual network activity"
echo "5. Keep dependencies updated from trusted sources"
fi
echo ""
echo -e "${BLUE}================================================================${NC}"
echo "For more information: https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack"
echo -e "${BLUE}================================================================${NC}"
# Cleanup
if [ -f "$IOC_FILE" ]; then
rm -f "$IOC_FILE"
fi
exit $INFECTED
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment