Last active
November 27, 2025 19:47
-
-
Save GraemeF/912372fd822a61ebeb70403b70011a8a to your computer and use it in GitHub Desktop.
Look for compromised packages
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 | |
| # Set the URL for the latest list of compromised packages | |
| COMPROMISED_LIST_URL="https://raw.githubusercontent.com/Cobenian/shai-hulud-detect/main/compromised-packages.txt" | |
| # Inform the user that the list is being downloaded | |
| echo "Downloading latest compromised packages list..." | |
| # Download the compromised packages list to a local file | |
| curl -s "$COMPROMISED_LIST_URL" -o compromised-packages.txt | |
| # Check if the download was successful (file exists and is not empty) | |
| if [ ! -s compromised-packages.txt ]; then | |
| echo "Failed to download compromised packages list. Exiting." | |
| exit 1 | |
| fi | |
| # Check if ripgrep is installed | |
| if ! command -v rg &> /dev/null; then | |
| echo "Error: ripgrep (rg) is not installed. Please install it first." | |
| echo " macOS: brew install ripgrep" | |
| echo " Ubuntu: apt install ripgrep" | |
| exit 1 | |
| fi | |
| # Build pattern file for grep -f style matching | |
| echo "Building regex patterns from compromised packages list..." | |
| patterns_file=$(mktemp) | |
| trap "rm -f $patterns_file" EXIT | |
| # Use process substitution to avoid subshell issue with while loop | |
| while read -r line; do | |
| # Skip comments and empty lines | |
| [[ "$line" =~ ^#.*$ || -z "$line" ]] && continue | |
| pkg=$(echo "$line" | cut -d':' -f1) | |
| ver=$(echo "$line" | cut -d':' -f2) | |
| # Escape special regex chars in package name (keep @ and / literal for scoped packages) | |
| escaped_pkg=$(echo "$pkg" | sed 's/[.[\*^$()+?{|]/\\&/g') | |
| # Escape dots in version to match literally | |
| escaped_ver=$(echo "$ver" | sed 's/\./\\./g') | |
| # Simple pattern like original script but with escaped version dots | |
| # Matches: pkg followed by version (with anything reasonable in between) | |
| echo "${escaped_pkg}.*${escaped_ver}" | |
| done < compromised-packages.txt > "$patterns_file" | |
| if [ ! -s "$patterns_file" ]; then | |
| echo "No patterns found in compromised packages list. Exiting." | |
| exit 1 | |
| fi | |
| # Count lock files to be scanned | |
| lockfile_count=$(rg --glob 'package-lock.json' --glob 'yarn.lock' --glob 'bun.lock' \ | |
| --glob '!**/node_modules/**' \ | |
| --files . 2>/dev/null | wc -l | tr -d ' ') | |
| echo " | |
| Scanning $lockfile_count lock file(s) for compromised packages... | |
| " | |
| # Use ripgrep with pattern file - includes bun.lock | |
| rg --glob 'package-lock.json' --glob 'yarn.lock' --glob 'bun.lock' \ | |
| --glob '!**/node_modules/**' \ | |
| -n -f "$patterns_file" . 2>/dev/null | |
| if [ $? -eq 0 ]; then | |
| echo " | |
| [WARNING] Compromised packages found above!" | |
| else | |
| echo "No compromised packages found." | |
| fi | |
| echo " | |
| Scan complete." |
Yikes! ripgrep skips hidden folders unless explicitly told to search them! Another addition to the above should probably be the -. or --hidden switch, because the main NPM cache and npx caches would likely otherwise be missed:
npm config get cache
> ~/.npm
Author
@RobinKnipe This was intended to scan repositories' lock files, not caches or the entire filesystem - feel free to adapt however you like!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://gist.github.com/GraemeF/912372fd822a61ebeb70403b70011a8a#file-scan-lockfiles-rg-sh-L58
might be worth replacing default current dir
.with the ability to specify other targets:${1:-.}, e.g.