Skip to content

Instantly share code, notes, and snippets.

@TechByTom
Last active September 10, 2025 22:09
Show Gist options
  • Save TechByTom/5fc89599dedda74b09ef2543348417a7 to your computer and use it in GitHub Desktop.
Save TechByTom/5fc89599dedda74b09ef2543348417a7 to your computer and use it in GitHub Desktop.
Check for 9-8-25 NPM Supply Chain Compromised packages (bash)
#!/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