Created
November 25, 2025 04:22
-
-
Save benkitzelman/dda0ca9f60a927e1116f5fc08a52c218 to your computer and use it in GitHub Desktop.
Shai-Hulud detection - have I been compromised
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 | |
| # NPM Supply Chain Attack Detector | |
| # Based on GitLab's analysis of "Sha1-Hulud" malware campaign | |
| # Reference: https://about.gitlab.com/blog/gitlab-discovers-widespread-npm-supply-chain-attack/ | |
| # Color codes | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| CYAN='\033[0;36m' | |
| BOLD='\033[1m' | |
| NC='\033[0m' # No Color | |
| ISSUES_FOUND=0 | |
| echo -e "${CYAN}${BOLD}╔════════════════════════════════════════════════════════╗${NC}" | |
| echo -e "${CYAN}${BOLD}║ NPM Supply Chain Attack Detection (Sha1-Hulud) ║${NC}" | |
| echo -e "${CYAN}${BOLD}╚════════════════════════════════════════════════════════╝${NC}" | |
| echo "" | |
| # Function to report findings | |
| report_finding() { | |
| local severity=$1 | |
| local message=$2 | |
| local path=$3 | |
| ISSUES_FOUND=$((ISSUES_FOUND + 1)) | |
| case $severity in | |
| CRITICAL) | |
| echo -e "${RED}${BOLD}[CRITICAL]${NC} $message" | |
| ;; | |
| HIGH) | |
| echo -e "${RED}[HIGH]${NC} $message" | |
| ;; | |
| MEDIUM) | |
| echo -e "${YELLOW}[MEDIUM]${NC} $message" | |
| ;; | |
| esac | |
| if [ ! -z "$path" ]; then | |
| echo -e " ${CYAN}→${NC} $path" | |
| fi | |
| echo "" | |
| } | |
| # Pre-scan: Safe package install (optional) | |
| if [ -f "package.json" ]; then | |
| echo -e "${CYAN}[PRE-SCAN]${NC} Optional: Install packages (without running pre / post install scripts) for deeper analysis" | |
| echo "─────────────────────────────────────────────────────────" | |
| echo -e " ${YELLOW}⚠${NC} Found package.json. Install packages with scripts disabled?" | |
| echo -e " This safely installs packages WITHOUT executing any scripts." | |
| echo -e " ${RED}Note:${NC} Will remove existing node_modules before clean install." | |
| echo "" | |
| read -p " Run package install? (y/N): " -n 1 -r | |
| echo "" | |
| if [[ $REPLY =~ ^[Yy]$ ]]; then | |
| echo "" | |
| echo -e " Which package manager are you using?" | |
| echo -e " ${BOLD}1)${NC} npm" | |
| echo -e " ${BOLD}2)${NC} yarn" | |
| echo "" | |
| read -p " Select (1 or 2): " -n 1 -r PACKAGE_MANAGER | |
| echo "" | |
| echo "" | |
| if [ -d "./node_modules" ]; then | |
| echo -e " ${YELLOW}⚠${NC} Removing existing node_modules..." | |
| rm -rf ./node_modules | |
| fi | |
| echo -e " ${YELLOW}⚠${NC} Installing packages with scripts disabled for safe analysis..." | |
| if [[ $PACKAGE_MANAGER == "1" ]]; then | |
| npm ci --no-audit --ignore-scripts 2>&1 | tail -n 5 | |
| echo -e " ${GREEN}✓${NC} Package installation complete (scripts blocked)" | |
| elif [[ $PACKAGE_MANAGER == "2" ]]; then | |
| YARN_ENABLE_SCRIPTS=false yarn install --frozen-lockfile | tail -n 5 | |
| echo -e " ${GREEN}✓${NC} Package installation complete (scripts blocked)" | |
| else | |
| echo -e " ${RED}✗${NC} Invalid selection. Skipping install." | |
| fi | |
| else | |
| echo -e " ${YELLOW}⊘${NC} Skipped package install (scan will only check existing node_modules)" | |
| fi | |
| echo "" | |
| fi | |
| # 1. Check for malicious files in node_modules | |
| echo -e "${CYAN}[1/7]${NC} Scanning for malicious files in node_modules..." | |
| echo "─────────────────────────────────────────────────────────" | |
| if [ -d "./node_modules" ]; then | |
| # Primary malware files | |
| MALICIOUS_FILES=( | |
| "setup_bun.js" | |
| "bun_environment.js" | |
| ) | |
| for file in "${MALICIOUS_FILES[@]}"; do | |
| results=$(find ./node_modules -name "$file" 2>/dev/null) | |
| if [ ! -z "$results" ]; then | |
| report_finding "CRITICAL" "Found malware loader/payload: $file" "$results" | |
| fi | |
| done | |
| # Exfiltration data files | |
| EXFIL_FILES=( | |
| "cloud.json" | |
| "contents.json" | |
| "environment.json" | |
| "truffleSecrets.json" | |
| ) | |
| for file in "${EXFIL_FILES[@]}"; do | |
| results=$(find ./node_modules -name "$file" 2>/dev/null) | |
| if [ ! -z "$results" ]; then | |
| report_finding "HIGH" "Found potential exfiltration data file: $file" "$results" | |
| fi | |
| done | |
| else | |
| echo -e " ${GREEN}✓${NC} No node_modules directory found" | |
| fi | |
| # 2. Check for Trufflehog cache directories | |
| echo "" | |
| echo -e "${CYAN}[2/7]${NC} Checking for Trufflehog scanner artifacts..." | |
| echo "─────────────────────────────────────────────────────────" | |
| TRUFFLEHOG_PATHS=( | |
| "$HOME/.truffler-cache" | |
| "$HOME/.truffler-cache/extract" | |
| "$HOME/.truffler-cache/trufflehog" | |
| "$HOME/.truffler-cache/trufflehog.exe" | |
| ) | |
| for path in "${TRUFFLEHOG_PATHS[@]}"; do | |
| if [ -e "$path" ]; then | |
| report_finding "CRITICAL" "Found Trufflehog cache (malware uses this for credential scanning)" "$path" | |
| fi | |
| done | |
| if [ $ISSUES_FOUND -eq 0 ] || [ ${#TRUFFLEHOG_PATHS[@]} -eq 0 ]; then | |
| echo -e " ${GREEN}✓${NC} No Trufflehog artifacts found" | |
| fi | |
| # 3. Check package.json files for suspicious preinstall scripts | |
| echo "" | |
| echo -e "${CYAN}[3/7]${NC} Analyzing package.json files for suspicious scripts..." | |
| echo "─────────────────────────────────────────────────────────" | |
| if [ -d "./node_modules" ]; then | |
| suspicious_packages=$(find ./node_modules -name "package.json" -type f -exec grep -l "setup_bun.js\|bun_environment.js" {} \; 2>/dev/null) | |
| if [ ! -z "$suspicious_packages" ]; then | |
| while IFS= read -r pkg; do | |
| report_finding "CRITICAL" "Package contains reference to malicious scripts" "$pkg" | |
| done <<< "$suspicious_packages" | |
| else | |
| echo -e " ${GREEN}✓${NC} No suspicious preinstall scripts detected" | |
| fi | |
| else | |
| echo -e " ${GREEN}✓${NC} No node_modules to scan" | |
| fi | |
| # 4. Check for GitHub repositories with "Sha1-Hulud" marker | |
| echo "" | |
| echo -e "${CYAN}[4/7]${NC} Checking for compromised GitHub repositories..." | |
| echo "─────────────────────────────────────────────────────────" | |
| if command -v gh &> /dev/null; then | |
| # Check if authenticated | |
| if gh auth status &> /dev/null; then | |
| repos=$(gh repo list --json name,description --limit 1000 2>/dev/null | grep -i "Sha1-Hulud" || true) | |
| if [ ! -z "$repos" ]; then | |
| report_finding "CRITICAL" "Found repository with Sha1-Hulud marker in your account!" "$repos" | |
| else | |
| echo -e " ${GREEN}✓${NC} No compromised repositories found in your account" | |
| fi | |
| else | |
| echo -e " ${YELLOW}⚠${NC} GitHub CLI not authenticated, skipping repo check" | |
| fi | |
| else | |
| echo -e " ${YELLOW}⚠${NC} GitHub CLI not installed, skipping repo check" | |
| fi | |
| # 5. Check for suspicious environment variables | |
| echo "" | |
| echo -e "${CYAN}[5/7]${NC} Scanning environment for suspicious tokens..." | |
| echo "─────────────────────────────────────────────────────────" | |
| # Check for common token patterns that might have been harvested | |
| suspicious_env=0 | |
| if env | grep -q "NPM_CONFIG_TOKEN"; then | |
| echo -e " ${YELLOW}⚠${NC} NPM_CONFIG_TOKEN found in environment (potential target for harvesting)" | |
| suspicious_env=1 | |
| fi | |
| if env | grep -E "ghp_|gho_" &> /dev/null; then | |
| echo -e " ${YELLOW}⚠${NC} GitHub token found in environment (potential target for harvesting)" | |
| suspicious_env=1 | |
| fi | |
| if [ $suspicious_env -eq 0 ]; then | |
| echo -e " ${GREEN}✓${NC} No obviously exposed tokens in environment" | |
| fi | |
| # 6. Check for large obfuscated JavaScript files | |
| echo "" | |
| echo -e "${CYAN}[6/7]${NC} Searching for suspiciously large obfuscated files..." | |
| echo "─────────────────────────────────────────────────────────" | |
| if [ -d "./node_modules" ]; then | |
| # Find JS files larger than 5MB (bun_environment.js is ~10MB) | |
| large_files=$(find ./node_modules -name "*.js" -type f -size +5M 2>/dev/null) | |
| if [ ! -z "$large_files" ]; then | |
| while IFS= read -r file; do | |
| # Check if file appears obfuscated (very long lines, no whitespace) | |
| first_line=$(head -n 1 "$file" | cut -c1-200) | |
| if [[ ${#first_line} -gt 150 ]] && [[ ! "$first_line" =~ [[:space:]] ]]; then | |
| report_finding "MEDIUM" "Found large, potentially obfuscated JavaScript file" "$file ($(du -h "$file" | cut -f1))" | |
| fi | |
| done <<< "$large_files" | |
| else | |
| echo -e " ${GREEN}✓${NC} No unusually large JavaScript files found" | |
| fi | |
| else | |
| echo -e " ${GREEN}✓${NC} No node_modules to scan" | |
| fi | |
| # 7. Check .github/workflows for suspicious patterns | |
| echo "" | |
| echo -e "${CYAN}[7/7]${NC} Checking GitHub workflows for malicious runners..." | |
| echo "─────────────────────────────────────────────────────────" | |
| if [ -d "./.github/workflows" ]; then | |
| # Look for self-hosted runners or suspicious workflow files | |
| suspicious_workflows=$(grep -r "runs-on: self-hosted\|discussion.yaml\|formatter_" ./.github/workflows/ 2>/dev/null || true) | |
| if [ ! -z "$suspicious_workflows" ]; then | |
| report_finding "MEDIUM" "Found potentially suspicious workflow configuration" "$suspicious_workflows" | |
| else | |
| echo -e " ${GREEN}✓${NC} No suspicious workflow patterns detected" | |
| fi | |
| else | |
| echo -e " ${GREEN}✓${NC} No .github/workflows directory" | |
| fi | |
| # Summary | |
| echo "" | |
| echo -e "${CYAN}${BOLD}╔════════════════════════════════════════════════════════╗${NC}" | |
| echo -e "${CYAN}${BOLD}║ SCAN COMPLETE ║${NC}" | |
| echo -e "${CYAN}${BOLD}╚════════════════════════════════════════════════════════╝${NC}" | |
| echo "" | |
| if [ $ISSUES_FOUND -eq 0 ]; then | |
| echo -e "${GREEN}${BOLD}✓ No indicators of compromise detected${NC}" | |
| echo "" | |
| echo -e "Your system appears clean. However, stay vigilant:" | |
| echo -e " • Regularly audit your npm packages" | |
| echo -e " • Review GitHub repository permissions" | |
| echo -e " • Monitor for unexpected package updates" | |
| exit 0 | |
| else | |
| echo -e "${RED}${BOLD}⚠ FOUND $ISSUES_FOUND POTENTIAL INDICATOR(S) OF COMPROMISE${NC}" | |
| echo "" | |
| echo -e "${YELLOW}RECOMMENDED ACTIONS:${NC}" | |
| echo -e " 1. ${BOLD}IMMEDIATELY${NC} rotate all GitHub, npm, and cloud provider tokens" | |
| echo -e " 2. Review all npm packages you maintain for unauthorized updates" | |
| echo -e " 3. Check GitHub repositories for the 'Sha1-Hulud' marker" | |
| echo -e " 4. Remove any infected packages from node_modules" | |
| echo -e " 5. Scan your filesystem with updated antivirus software" | |
| echo -e " 6. ${RED}${BOLD}DO NOT${NC} mass-revoke tokens (may trigger dead man's switch)" | |
| echo "" | |
| echo -e "${YELLOW}IMPORTANT:${NC} This malware contains a destructive payload that triggers" | |
| echo -e "if it loses access to both GitHub and npm. Coordinate remediation carefully." | |
| echo "" | |
| exit 1 | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment