Last active
September 10, 2025 22:09
-
-
Save TechByTom/5fc89599dedda74b09ef2543348417a7 to your computer and use it in GitHub Desktop.
Check for 9-8-25 NPM Supply Chain Compromised packages (bash)
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 bash | |
| # NPM Compromise Checker Script | |
| # Checks for known compromised npm packages from September 2025 supply chain attack | |
| echo "================================================" | |
| echo "NPM Security Compromise Checker" | |
| echo "================================================" | |
| echo "" | |
| # Color codes for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| NC='\033[0m' # No Color | |
| # Compromised packages and versions from September 2025 attack | |
| # Using parallel arrays for compatibility with older bash versions (macOS) | |
| COMPROMISED_NAMES=( | |
| "chalk" | |
| "debug" | |
| "ansi-regex" | |
| "ansi-styles" | |
| "color-convert" | |
| "color-name" | |
| "strip-ansi" | |
| "supports-color" | |
| "wrap-ansi" | |
| "backslash" | |
| "chalk-template" | |
| "color-string" | |
| "error-ex" | |
| "has-ansi" | |
| "is-arrayish" | |
| "simple-swizzle" | |
| "slice-ansi" | |
| "supports-hyperlinks" | |
| ) | |
| COMPROMISED_VERSIONS=( | |
| "5.6.1" | |
| "4.4.2" | |
| "6.2.1" | |
| "6.2.2" | |
| "3.1.1" | |
| "2.0.1" | |
| "7.1.1" | |
| "10.2.1" | |
| "9.0.1" | |
| "0.2.1" | |
| "1.1.1" | |
| "2.1.1" | |
| "1.3.3" | |
| "6.0.1" | |
| "0.3.3" | |
| "0.2.3" | |
| "7.1.1" | |
| "4.1.1" | |
| ) | |
| # Function to get compromised version for a package | |
| get_compromised_version() { | |
| local pkg="$1" | |
| for i in "${!COMPROMISED_NAMES[@]}"; do | |
| if [ "${COMPROMISED_NAMES[$i]}" = "$pkg" ]; then | |
| echo "${COMPROMISED_VERSIONS[$i]}" | |
| return | |
| fi | |
| done | |
| echo "" | |
| } | |
| # Check if npm is installed | |
| if ! command -v npm &> /dev/null; then | |
| echo -e "${RED}npm is not installed on this system${NC}" | |
| exit 1 | |
| fi | |
| echo "npm version: $(npm --version)" | |
| echo "Registry: $(npm config get registry)" | |
| echo "" | |
| # Track if any compromised packages are found | |
| FOUND_COMPROMISED=0 | |
| FOUND_PACKAGES=() | |
| echo "Checking global packages..." | |
| echo "----------------------------" | |
| # First, get all global package paths | |
| echo "Scanning all global packages for matches..." | |
| GLOBAL_PACKAGES=$(npm list -g --depth=0 2>/dev/null | grep -E "^[├└]" | sed -E 's/^[├└]─[─ ]+//' | cut -d'@' -f1) | |
| echo "" | |
| # Check each compromised package name | |
| for i in "${!COMPROMISED_NAMES[@]}"; do | |
| package="${COMPROMISED_NAMES[$i]}" | |
| compromised_version="${COMPROMISED_VERSIONS[$i]}" | |
| echo -e "${YELLOW}Checking for: $package${NC}" | |
| # Find all instances of this package globally (including dependencies) | |
| PACKAGE_INSTANCES=$(npm list -g "$package" 2>/dev/null | grep -E "$package@" | sed -E "s/.*($package@[0-9.]+).*/\1/") | |
| if [ ! -z "$PACKAGE_INSTANCES" ]; then | |
| while IFS= read -r instance; do | |
| VERSION=$(echo "$instance" | cut -d'@' -f2) | |
| LOCATION=$(npm list -g "$package" 2>/dev/null | grep -B1 "$instance" | head -1 | sed 's/^[ ├└─│]*//') | |
| echo -e " Found: $instance" | |
| echo -e " Location: Global (via $LOCATION)" | |
| echo -e " Compromised version: $compromised_version" | |
| if [ "$VERSION" == "$compromised_version" ]; then | |
| echo -e " Status: ${RED}✗ COMPROMISED - EXACT MATCH${NC}" | |
| FOUND_COMPROMISED=1 | |
| FOUND_PACKAGES+=("$package@$VERSION (global)") | |
| else | |
| echo -e " Status: ${GREEN}✓ SAFE - Different version${NC}" | |
| fi | |
| echo "" | |
| done <<< "$PACKAGE_INSTANCES" | |
| else | |
| echo -e " Not found globally" | |
| echo "" | |
| fi | |
| done | |
| echo "" | |
| echo "Checking local projects..." | |
| echo "----------------------------" | |
| # Find all package.json files in home directory (limit depth to avoid long searches) | |
| PROJECT_COUNT=0 | |
| for PACKAGE_JSON in $(find ~ -maxdepth 5 -name "package.json" -type f 2>/dev/null); do | |
| PROJECT_DIR=$(dirname "$PACKAGE_JSON") | |
| echo -e "${YELLOW}Checking project: $PROJECT_DIR${NC}" | |
| PROJECT_COUNT=$((PROJECT_COUNT + 1)) | |
| # Check each compromised package in this project | |
| for i in "${!COMPROMISED_NAMES[@]}"; do | |
| package="${COMPROMISED_NAMES[$i]}" | |
| compromised_version="${COMPROMISED_VERSIONS[$i]}" | |
| # Check both direct dependencies and nested ones | |
| if [ -d "$PROJECT_DIR/node_modules" ]; then | |
| # Find all instances of this package in node_modules | |
| PACKAGE_PATHS=$(find "$PROJECT_DIR/node_modules" -name "$package" -type d 2>/dev/null) | |
| if [ ! -z "$PACKAGE_PATHS" ]; then | |
| while IFS= read -r pkg_path; do | |
| if [ -f "$pkg_path/package.json" ]; then | |
| INSTALLED_VERSION=$(grep '"version"' "$pkg_path/package.json" 2>/dev/null | head -1 | sed -E 's/.*"version": "([^"]+)".*/\1/') | |
| # Get relative path for cleaner output | |
| REL_PATH=${pkg_path#$PROJECT_DIR/} | |
| echo -e " ${YELLOW}Package: $package${NC}" | |
| echo -e " Found: $package@$INSTALLED_VERSION" | |
| echo -e " Location: $REL_PATH" | |
| echo -e " Compromised version: $compromised_version" | |
| if [ "$INSTALLED_VERSION" == "$compromised_version" ]; then | |
| echo -e " Status: ${RED}✗ COMPROMISED - EXACT MATCH${NC}" | |
| FOUND_COMPROMISED=1 | |
| FOUND_PACKAGES+=("$package@$INSTALLED_VERSION in $PROJECT_DIR") | |
| else | |
| echo -e " Status: ${GREEN}✓ SAFE - Different version${NC}" | |
| fi | |
| echo "" | |
| fi | |
| done <<< "$PACKAGE_PATHS" | |
| fi | |
| fi | |
| done | |
| done | |
| if [ $PROJECT_COUNT -eq 0 ]; then | |
| echo "No npm projects found in home directory" | |
| fi | |
| echo "" | |
| echo "================================================" | |
| echo "HISTORICAL CHECKS" | |
| echo "================================================" | |
| echo "Checking for evidence of previously installed compromised versions..." | |
| echo "" | |
| # Track historical findings | |
| HISTORICAL_FOUND=0 | |
| HISTORICAL_EVIDENCE=() | |
| # Check npm cache for compromised versions | |
| echo -e "${YELLOW}Checking npm cache...${NC}" | |
| if [ -d ~/.npm ]; then | |
| for i in "${!COMPROMISED_NAMES[@]}"; do | |
| package="${COMPROMISED_NAMES[$i]}" | |
| COMPROMISED_VERSION="${COMPROMISED_VERSIONS[$i]}" | |
| # Check if the specific version exists in cache | |
| CACHE_CHECK=$(npm cache ls 2>/dev/null | grep "$package@$COMPROMISED_VERSION" || true) | |
| if [ ! -z "$CACHE_CHECK" ]; then | |
| echo -e "${RED} ⚠ Found in cache: $package@$COMPROMISED_VERSION${NC}" | |
| HISTORICAL_FOUND=1 | |
| HISTORICAL_EVIDENCE+=("$package@$COMPROMISED_VERSION found in npm cache") | |
| fi | |
| done | |
| if [ $HISTORICAL_FOUND -eq 0 ]; then | |
| echo -e "${GREEN} No compromised versions found in npm cache${NC}" | |
| fi | |
| else | |
| echo " npm cache directory not found" | |
| fi | |
| echo "" | |
| # Check git history for package-lock.json | |
| echo -e "${YELLOW}Checking git history for package-lock.json...${NC}" | |
| GIT_REPOS_CHECKED=0 | |
| for dir in $(find ~ -maxdepth 5 -type d -name ".git" 2>/dev/null | head -20); do | |
| REPO_DIR=$(dirname "$dir") | |
| if [ -f "$REPO_DIR/package-lock.json" ]; then | |
| GIT_REPOS_CHECKED=$((GIT_REPOS_CHECKED + 1)) | |
| echo " Checking repository: $REPO_DIR" | |
| cd "$REPO_DIR" 2>/dev/null || continue | |
| for i in "${!COMPROMISED_NAMES[@]}"; do | |
| package="${COMPROMISED_NAMES[$i]}" | |
| COMPROMISED_VERSION="${COMPROMISED_VERSIONS[$i]}" | |
| # Search git history for the compromised version | |
| GIT_HISTORY=$(git log -p --all -S "\"$package\": {" -- package-lock.json 2>/dev/null | grep -A5 "\"$package\"" | grep "\"version\": \"$COMPROMISED_VERSION\"" || true) | |
| if [ ! -z "$GIT_HISTORY" ]; then | |
| echo -e "${RED} ⚠ Historical evidence: $package@$COMPROMISED_VERSION was previously installed${NC}" | |
| # Get the date when it was installed | |
| COMMIT_DATE=$(git log --all -S "\"version\": \"$COMPROMISED_VERSION\"" --format="%ai" -- package-lock.json 2>/dev/null | head -1) | |
| if [ ! -z "$COMMIT_DATE" ]; then | |
| echo -e "${RED} First seen: $COMMIT_DATE${NC}" | |
| fi | |
| HISTORICAL_FOUND=1 | |
| HISTORICAL_EVIDENCE+=("$package@$COMPROMISED_VERSION previously in $REPO_DIR") | |
| fi | |
| done | |
| cd "$OLDPWD" > /dev/null 2>&1 | |
| fi | |
| done | |
| if [ $GIT_REPOS_CHECKED -eq 0 ]; then | |
| echo " No git repositories with package-lock.json found" | |
| fi | |
| echo "" | |
| # Check bash history for npm install commands | |
| echo -e "${YELLOW}Checking shell history...${NC}" | |
| HISTORY_FILES=(~/.bash_history ~/.zsh_history ~/.history) | |
| HISTORY_CHECKED=0 | |
| for hist_file in "${HISTORY_FILES[@]}"; do | |
| if [ -f "$hist_file" ]; then | |
| HISTORY_CHECKED=1 | |
| for i in "${!COMPROMISED_NAMES[@]}"; do | |
| package="${COMPROMISED_NAMES[$i]}" | |
| COMPROMISED_VERSION="${COMPROMISED_VERSIONS[$i]}" | |
| # Check for specific version installations | |
| HIST_CHECK=$(grep -E "npm (install|i|add).*$package@$COMPROMISED_VERSION" "$hist_file" 2>/dev/null || true) | |
| if [ ! -z "$HIST_CHECK" ]; then | |
| echo -e "${RED} ⚠ Found in history: Installation of $package@$COMPROMISED_VERSION${NC}" | |
| echo -e "${RED} Command: $HIST_CHECK${NC}" | |
| HISTORICAL_FOUND=1 | |
| HISTORICAL_EVIDENCE+=("Installation command for $package@$COMPROMISED_VERSION in shell history") | |
| fi | |
| done | |
| fi | |
| done | |
| if [ $HISTORY_CHECKED -eq 0 ]; then | |
| echo " No shell history files found" | |
| fi | |
| echo "" | |
| # Check yarn cache if yarn is installed | |
| if command -v yarn &> /dev/null; then | |
| echo -e "${YELLOW}Checking yarn cache...${NC}" | |
| for i in "${!COMPROMISED_NAMES[@]}"; do | |
| package="${COMPROMISED_NAMES[$i]}" | |
| COMPROMISED_VERSION="${COMPROMISED_VERSIONS[$i]}" | |
| YARN_CHECK=$(yarn cache list 2>/dev/null | grep "$package@$COMPROMISED_VERSION" || true) | |
| if [ ! -z "$YARN_CHECK" ]; then | |
| echo -e "${RED} ⚠ Found in yarn cache: $package@$COMPROMISED_VERSION${NC}" | |
| HISTORICAL_FOUND=1 | |
| HISTORICAL_EVIDENCE+=("$package@$COMPROMISED_VERSION found in yarn cache") | |
| fi | |
| done | |
| echo "" | |
| fi | |
| # Check file modification times for recently updated packages | |
| echo -e "${YELLOW}Checking package modification times (packages updated after Sept 2025)...${NC}" | |
| SUSPECT_DATE="2025-09-01" | |
| for PACKAGE_JSON in $(find ~ -maxdepth 5 -path "*/node_modules/*/package.json" -type f 2>/dev/null | head -50); do | |
| PKG_DIR=$(dirname "$PACKAGE_JSON") | |
| PKG_NAME=$(basename "$PKG_DIR") | |
| # Check if this is one of the compromised packages | |
| IS_COMPROMISED=0 | |
| COMP_VERSION="" | |
| for idx in "${!COMPROMISED_NAMES[@]}"; do | |
| if [ "${COMPROMISED_NAMES[$idx]}" = "$PKG_NAME" ]; then | |
| IS_COMPROMISED=1 | |
| COMP_VERSION="${COMPROMISED_VERSIONS[$idx]}" | |
| break | |
| fi | |
| done | |
| if [ $IS_COMPROMISED -eq 1 ]; then | |
| # Get modification time | |
| if [[ "$OSTYPE" == "darwin"* ]]; then | |
| MOD_DATE=$(stat -f "%Sm" -t "%Y-%m-%d" "$PACKAGE_JSON" 2>/dev/null) | |
| else | |
| MOD_DATE=$(stat -c "%y" "$PACKAGE_JSON" 2>/dev/null | cut -d' ' -f1) | |
| fi | |
| if [ ! -z "$MOD_DATE" ] && [[ "$MOD_DATE" > "$SUSPECT_DATE" ]]; then | |
| INSTALLED_VERSION=$(grep '"version"' "$PACKAGE_JSON" 2>/dev/null | head -1 | sed -E 's/.*"version": "([^"]+)".*/\1/') | |
| echo -e "${YELLOW} Package recently modified: $PKG_NAME@$INSTALLED_VERSION${NC}" | |
| echo -e " Location: $PKG_DIR" | |
| echo -e " Modified: $MOD_DATE" | |
| echo -e " Compromised version was: $COMP_VERSION" | |
| if [ "$INSTALLED_VERSION" != "$COMP_VERSION" ]; then | |
| echo -e "${YELLOW} Possibly updated from compromised version${NC}" | |
| fi | |
| fi | |
| fi | |
| done | |
| echo "" | |
| echo "" | |
| echo "================================================" | |
| echo "COMPROMISED PACKAGE REFERENCE" | |
| echo "================================================" | |
| echo "The following packages and versions were compromised:" | |
| echo "" | |
| for i in "${!COMPROMISED_NAMES[@]}"; do | |
| echo " ${COMPROMISED_NAMES[$i]} @ ${COMPROMISED_VERSIONS[$i]}" | |
| done | sort | |
| echo "" | |
| echo "================================================" | |
| echo "SCAN RESULTS" | |
| echo "================================================" | |
| # Check both current and historical findings | |
| if [ $FOUND_COMPROMISED -eq 1 ] || [ $HISTORICAL_FOUND -eq 1 ]; then | |
| if [ $FOUND_COMPROMISED -eq 1 ]; then | |
| echo -e "${RED}⚠ WARNING: Compromised packages currently installed!${NC}" | |
| echo "" | |
| echo "Currently installed compromised packages:" | |
| for pkg in "${FOUND_PACKAGES[@]}"; do | |
| echo " - $pkg" | |
| done | |
| echo "" | |
| fi | |
| if [ $HISTORICAL_FOUND -eq 1 ]; then | |
| echo -e "${YELLOW}⚠ HISTORICAL WARNING: Evidence of previously installed compromised versions${NC}" | |
| echo "" | |
| echo "Historical evidence found:" | |
| for evidence in "${HISTORICAL_EVIDENCE[@]}"; do | |
| echo " - $evidence" | |
| done | |
| echo "" | |
| echo -e "${YELLOW}Even though compromised versions may have been updated, the malware could have:${NC}" | |
| echo " - Stolen authentication tokens or credentials" | |
| echo " - Modified cryptocurrency wallet addresses in your clipboard" | |
| echo " - Injected persistent malware into other files" | |
| echo "" | |
| fi | |
| echo "RECOMMENDED ACTIONS:" | |
| echo "1. Update affected packages immediately:" | |
| echo " npm update [package-name] (for local packages)" | |
| echo " npm update -g [package-name] (for global packages)" | |
| echo "" | |
| echo "2. Clear npm cache completely:" | |
| echo " npm cache clean --force" | |
| echo "" | |
| echo "3. Audit all projects:" | |
| echo " npm audit fix" | |
| echo "" | |
| echo "4. Rotate all npm tokens and credentials:" | |
| echo " npm token revoke [token-id]" | |
| echo " npm login (to create new tokens)" | |
| echo "" | |
| echo "5. Check for suspicious activity:" | |
| echo " - Review cryptocurrency transaction history" | |
| echo " - Check browser extensions for modifications" | |
| echo " - Review git commits made during the compromise period" | |
| echo "" | |
| if [ $HISTORICAL_FOUND -eq 1 ]; then | |
| echo "6. Since historical evidence was found, also:" | |
| echo " - Change passwords for npm and related services" | |
| echo " - Review system for any persistent malware" | |
| echo " - Check ~/.npmrc for suspicious registry entries" | |
| fi | |
| else | |
| echo -e "${GREEN}✓ No compromised packages detected (current or historical)${NC}" | |
| echo "" | |
| echo "Your system appears to be safe from the September 2025 npm supply chain attack." | |
| echo "No evidence of current or previous installation of compromised versions." | |
| fi | |
| echo "" | |
| echo "Additional security checks:" | |
| echo "----------------------------" | |
| # Check for suspicious npm scripts | |
| echo -n "Checking for suspicious install scripts... " | |
| SUSPICIOUS_SCRIPTS=$(find ~ -maxdepth 5 -name "package.json" -type f 2>/dev/null -exec grep -l '"preinstall"\|"postinstall"' {} \; | wc -l) | |
| if [ $SUSPICIOUS_SCRIPTS -gt 0 ]; then | |
| echo -e "${YELLOW}Found $SUSPICIOUS_SCRIPTS projects with install scripts (review manually)${NC}" | |
| else | |
| echo -e "${GREEN}None found${NC}" | |
| fi | |
| # Check npm token status | |
| echo -n "Checking npm authentication... " | |
| if npm whoami &>/dev/null; then | |
| echo -e "${YELLOW}Logged in as: $(npm whoami)${NC}" | |
| echo " Run 'npm token list' to review active tokens" | |
| else | |
| echo -e "${GREEN}Not logged in${NC}" | |
| fi | |
| echo "" | |
| echo "Scan complete at: $(date)" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment