Skip to content

Instantly share code, notes, and snippets.

@tonidy
Last active September 9, 2025 09:20
Show Gist options
  • Save tonidy/e32479e2b2be8e26b8026a5f62665d9a to your computer and use it in GitHub Desktop.
Save tonidy/e32479e2b2be8e26b8026a5f62665d9a to your computer and use it in GitHub Desktop.
Script to Check Vulnerable Packages
#!/usr/bin/env bash
set -euo pipefail
# Story: https://jdstaerk.substack.com/p/we-just-found-malicious-code-in-the
# Enhanced from:
# https://github.com/AndrewMohawk/RandomScripts/blob/main/scan_for_deps_qix-2025-08-09.sh
# ===== Vulnerable package list =====
VULNS="[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]"
# ===== Arguments =====
SCAN_ROOT="."
USE_LOCKFILE=0
for arg in "$@"; do
case "$arg" in
--lockfile) USE_LOCKFILE=1 ;;
*) SCAN_ROOT="$arg" ;;
esac
done
echo "πŸ” Scanning in: $SCAN_ROOT (lockfile fallback: $([[ $USE_LOCKFILE -eq 1 ]] && echo ON || echo OFF))"
echo "================================================"
TOTAL_AFFECTED=0
TOTAL_SCANNED=0
# === Helpers ===
match_vulns() { grep -Fxf <(printf '%s\n' "$VULNS") || true; }
# ---- Parsers: lockfiles ----
# npm package-lock.json (v1 & v2+)
scan_package_lock() {
jq -r '
if has("packages") then
.packages
| to_entries[]
| select(.value.version != null)
| "\(.value.name // (.key|sub("^node_modules/";"")))@\(.value.version)"
else
def walkdeps($k):
to_entries[]
| "\(.key)@\(.value.version)",
( .value.dependencies? // {} | walkdeps(.key) );
(.dependencies // {}) | walkdeps("")
end
' "$1" 2>/dev/null | sort -u
}
# pnpm pnpm-lock.yaml
scan_pnpm_lock() {
awk '
$0 ~ /^packages:/ {inpk=1; next}
inpk && $0 ~ /^[[:space:]]*\/.*\/[0-9][^:]*:/ {
line=$0
gsub(/^[[:space:]]*\/|:.*/,"",line)
n=split(line, a, "/")
if (n>=2) {
ver=a[n]; a[n]=""
name=line; sub("/"ver"$","",name)
print name"@"ver
}
}
' "$1" | sort -u
}
# yarn yarn.lock (v1 & simple berry)
scan_yarn_lock() {
awk '
/^[^[:space:]].*:?$/ { key=$0; sub(/:$/,"",key); inkey=1; next }
inkey && $1=="version" {
ver=$2; gsub(/"/,"",ver)
name=key; gsub(/^"/,"",name); gsub(/"$/,"",name)
n=split(name, parts, /, /)
for (i=1; i<=n; i++) {
k=parts[i]
lastat=0
for (j=1; j<=length(k); j++) if (substr(k,j,1)=="@") lastat=j
if (lastat>1) { pkg=substr(k,1,lastat-1); print pkg"@"ver }
}
inkey=0
}
' "$1" | sort -u
}
# deno deno.lock (v2/v3); extract npm entries
scan_deno_lock() {
jq -r '
if has("packages") then
(.packages.npm // {}) | to_entries[]? | .key
elif has("npm") then
(.npm.packages // .npm) | to_entries[]? | .key
else empty end
' "$1" 2>/dev/null \
| sed -E 's/^npm://g' \
| sort -u
}
# bun bun.lock (plaintext, v1.1+)
scan_bun_lock() {
local lock="$1"
# Format example:
# [packages]
# [email protected] = { ... }
awk '
/^\[packages\]/ {inpk=1; next}
inpk && /^[^[:space:]].* =/ {
key=$1
gsub(/=.*/,"",key)
gsub(/"/,"",key)
print key
}
' "$lock" | sort -u
}
# bun bun.lockb (legacy binary format)
scan_bun_lockb() {
local dir="$1"
if ! command -v bun >/dev/null 2>&1; then
echo "(bun not installed; skipping bun.lockb)" >&2
return 0
fi
(cd "$dir" && bun pm ls --json 2>/dev/null || true) \
| jq -r '.. | objects | select(has("name") and has("version")) | "\(.name)@\(.version)"' \
| sort -u
}
# === Main loop ===
while IFS= read -r pkg; do
dir="$(dirname "$pkg")"
TOTAL_SCANNED=$((TOTAL_SCANNED+1))
echo "πŸ“‚ Project: $dir"
if [[ -d "$dir/node_modules" ]]; then
installed=$(
(cd "$dir" && npm ls --all --depth=Infinity --json 2>/dev/null || true) \
| jq -r '.. | objects | select(has("name") and has("version")) | "\(.name)@\(.version)"' \
| sort -u
)
hits=$(printf '%s\n' "$installed" | match_vulns)
if [[ -n "$hits" ]]; then
echo " ❌ Vulnerable (installed):"
printf '%s\n' "$hits" | sed 's/^/ - /'
TOTAL_AFFECTED=$((TOTAL_AFFECTED+1))
else
echo " βœ… Safe (installed)"
fi
else
echo " ⏭ No node_modules"
if [[ $USE_LOCKFILE -eq 1 ]]; then
has_any=0
if [[ -f "$dir/package-lock.json" ]]; then
has_any=1
lhits=$(scan_package_lock "$dir/package-lock.json" | match_vulns)
if [[ -n "$lhits" ]]; then
echo " ❌ Vulnerable (package-lock.json):"
printf '%s\n' "$lhits" | sed 's/^/ - /'
TOTAL_AFFECTED=$((TOTAL_AFFECTED+1))
else
echo " βœ… Safe in package-lock.json"
fi
fi
if [[ -f "$dir/yarn.lock" ]]; then
has_any=1
yhits=$(scan_yarn_lock "$dir/yarn.lock" | match_vulns)
if [[ -n "$yhits" ]]; then
echo " ❌ Vulnerable (yarn.lock):"
printf '%s\n' "$yhits" | sed 's/^/ - /'
TOTAL_AFFECTED=$((TOTAL_AFFECTED+1))
else
echo " βœ… Safe in yarn.lock"
fi
fi
if [[ -f "$dir/pnpm-lock.yaml" ]]; then
has_any=1
phits=$(scan_pnpm_lock "$dir/pnpm-lock.yaml" | match_vulns)
if [[ -n "$phits" ]]; then
echo " ❌ Vulnerable (pnpm-lock.yaml):"
printf '%s\n' "$phits" | sed 's/^/ - /'
TOTAL_AFFECTED=$((TOTAL_AFFECTED+1))
else
echo " βœ… Safe in pnpm-lock.yaml"
fi
fi
if [[ -f "$dir/deno.lock" ]]; then
has_any=1
dhits=$(scan_deno_lock "$dir/deno.lock" | match_vulns)
if [[ -n "$dhits" ]]; then
echo " ❌ Vulnerable (deno.lock):"
printf '%s\n' "$dhits" | sed 's/^/ - /'
TOTAL_AFFECTED=$((TOTAL_AFFECTED+1))
else
echo " βœ… Safe in deno.lock"
fi
fi
if [[ -f "$dir/bun.lock" ]]; then
has_any=1
blhits=$(scan_bun_lock "$dir/bun.lock" | match_vulns)
if [[ -n "$blhits" ]]; then
echo " ❌ Vulnerable (bun.lock):"
printf '%s\n' "$blhits" | sed 's/^/ - /'
TOTAL_AFFECTED=$((TOTAL_AFFECTED+1))
else
echo " βœ… Safe in bun.lock"
fi
fi
if [[ -f "$dir/bun.lockb" ]]; then
has_any=1
bpkgs=$(scan_bun_lockb "$dir" || true)
bhits=$(printf '%s\n' "$bpkgs" | match_vulns)
if [[ -n "$bhits" ]]; then
echo " ❌ Vulnerable (bun.lockb):"
printf '%s\n' "$bhits" | sed 's/^/ - /'
TOTAL_AFFECTED=$((TOTAL_AFFECTED+1))
else
echo " βœ… Safe in bun.lockb"
fi
fi
[[ $has_any -eq 0 ]] && echo " (no lockfile found)"
else
echo " (hint: use --lockfile to check from lockfiles)"
fi
fi
echo "------------------------------------------------"
done < <(find "$SCAN_ROOT" -name package.json -not -path "*/node_modules/*" | sort)
echo "πŸ“Š Summary: $TOTAL_AFFECTED of $TOTAL_SCANNED projects contain vulnerable versions."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment