autorenew
NPM Supply Chain Attack September 2025: Protect Your Blockchain Projects from Compromised Packages

NPM Supply Chain Attack September 2025: Protect Your Blockchain Projects from Compromised Packages

In the fast-paced world of blockchain and meme token creation, staying on top of security threats is crucial. Yesterday, on September 8, 2025, the JavaScript community was rocked by one of the largest supply chain attacks on NPM, the Node Package Manager. Attackers compromised a contributor's account through phishing and pushed malicious versions of 18 widely used packages, injecting cryptostealer malware designed to hijack wallet transactions. This isn't just a general web dev issue—it's a direct hit on crypto enthusiasts, as the malware targets cryptocurrencies like Bitcoin, Ethereum, and Solana by rerouting funds to attacker-controlled addresses.

Understanding the Attack

The breach affected staples of the JS ecosystem, packages that power everything from colorful console logs to ANSI string handling. According to security reports from Aikido Security and Semgrep, the compromised versions included obfuscated JavaScript code that hooks into browser functions like fetch and XMLHttpRequest. It scans for wallet interactions, replaces legitimate addresses with lookalikes, and alters transaction parameters right before signing—all while keeping the user interface looking normal.

Here's a list of the affected packages and their compromised versions:

These packages rack up billions of downloads weekly, meaning they're likely lurking in the dependency trees of countless projects. For blockchain practitioners, this is especially alarming. Meme token launches often involve quick-built frontends, DEX interfaces, or bots using tools like React or Node.js, which pull in these utilities. If your dApp or token sniping script depends on one of these, you could unknowingly expose users—or yourself—to fund drains.

The good news? The community reacted swiftly. The malicious versions were yanked from NPM before many downloads occurred, limiting the damage. But if you've installed packages recently, it's time to check.

Edgar Pavlovsky's Timely Script

Enter Edgar Pavlovsky, a convexity expert and contributor to projects like Dark Research AI and Paladin Solana. In a tweet thread that quickly gained traction, he shared a bash script to scan your package.json dependency tree for these compromised packages. It's a straightforward tool that not only checks for known bad versions but also hunts for obfuscated malware patterns in your node_modules.

Edgar updated the script shortly after, enhancing it to detect those sneaky patterns. You can grab it from his GitHub Gist. Here's the full script for easy reference:

bash
#!/usr/bin/env bash

check-deps-with-comp.sh

set -euo pipefail
[[ "${DEBUG:-0}" == "1" ]] && set -x
TOOL="${TOOL:-npm}"

Watchlist lines can be: "name" or "name<tab/space>compromised_version"

WATCHLIST="$(cat <<'EOF'
backslash 0.2.1
chalk-template 1.1.1
supports-hyperlinks 4.1.1
has-ansi 6.0.1
simple-swizzle 0.2.3
color-string 2.1.1
error-ex 1.3.3
color-name 2.0.1
is-arrayish 0.3.3
slice-ansi 7.1.1
color-convert 3.1.1
wrap-ansi 9.0.1
ansi-regex 6.2.1
supports-color 10.2.1
strip-ansi 7.1.1
chalk 5.6.1
debug 4.4.2
ansi-styles 6.2.2
EOF
)"
command -v jq >/dev/null || { echo "[x] jq not found"; exit 1; }
[[ -f package.json ]] || { echo "[x] No package.json here"; exit 1; }
TMPDIR="$(mktemp -d)"
DEPS_JSON="$TMPDIR/deps.json"
MAP_JSON="$TMPDIR/name_map.json"
echo "[] Collecting dependency tree."
if [[ "$TOOL" == "bun" ]]; then
bun pm ls --json > "$DEPS_JSON"
else
npm ls --all --json > "$DEPS_JSON" || true
fi
echo "[
] Building name→{versions,roots} map…"
jq '
def walkdeps(root):
(.dependencies // {}) | to_entries[] as $e
| ($e.value | {name:$e.key, version:(.version // ""), root:(root // $e.key)})
, ($e.value | walkdeps(root // $e.key));
reduce (walkdeps(null)) as $n
({};
if ($n.version|type)=="string" and ($n.version|length)>0 then
.[$n.name] |= ( . // {versions:[], roots:[]} ) |
.[$n.name].versions += [$n.version] |
.[$n.name].roots += [$n.root]
else . end
)
| with_entries(
.value.versions |= (unique|sort) |
.value.roots |= (unique|sort)
)
' "$DEPS_JSON" > "$MAP_JSON"
if [[ "$(jq 'length' "$MAP_JSON")" -eq 0 ]]; then
echo "[!] Dependency map is empty. Did you run npm/bun install?"
fi
echo "[*] Checking watchlist (any version)…"

Collect all table data first to calculate column widths

declare -a table_data=()
declare -a col1_data=() col2_data=() col3_data=() col4_data=() col5_data=()

Add header row

col1_data+=("package")
col2_data+=("compromised version")
col3_data+=("present?")
col4_data+=("versions found")
col5_data+=("matches compromised?")
found_any=false
while IFS= read -r line; do
[[ -z "$line" ]] && continue

name = first field, compromised = second field if present

name="$(awk '{print $1}' <<<"$line")"
compromised="$(awk 'NF>1{print $2}' <<<"$line")"
[[ -z "${compromised:-}" ]] && compromised="-"
has=$(jq -r --arg n "$name" 'has($n)' "$MAP_JSON")
if [[ "$has" == "true" ]]; then
versions_csv=$(jq -r --arg n "$name" '.[$n].versions | join(", ")' "$MAP_JSON")
match="-"
if [[ "$compromised" != "-" ]]; then

exact version match?

matched=$(jq -r --arg n "$name" --arg v "$compromised" '
(.[$n].versions // []) | index($v) | if .==null then "no" else "yes" end
' "$MAP_JSON")
match="$matched"
fi
if [[ "$match" == "yes" ]]; then found_any=true; fi
else
versions_csv="-"
match="-"
fi
col1_data+=("$name")
col2_data+=("$compromised")
col3_data+=("$has")
col4_data+=("$versions_csv")
col5_data+=("$match")
done <<<"$WATCHLIST"

Calculate max widths

max_c1=0 max_c2=0 max_c3=0 max_c4=0 max_c5=0
for ((i=0; i<${#col1_data[@]}; i++)); do
(( ${#col1_data[i]} > max_c1 )) && max_c1=${#col1_data[i]}
(( ${#col2_data[i]} > max_c2 )) && max_c2=${#col2_data[i]}
(( ${#col3_data[i]} > max_c3 )) && max_c3=${#col3_data[i]}
(( ${#col4_data[i]} > max_c4 )) && max_c4=${#col4_data[i]}
(( ${#col5_data[i]} > max_c5 )) && max_c5=${#col5_data[i]}
done

Print table

for ((i=0; i<${#col1_data[@]}; i++)); do
printf "%-*s | %-*s | %-*s | %-*s | %-s\n"
$max_c1 "${col1_data[i]}"
$max_c2 "${col2_data[i]}"
$max_c3 "${col3_data[i]}"
$max_c4 "${col4_data[i]}"
$max_c5 "${col5_data[i]}"
done
echo "[
] Checking for malware patterns in node_modules..."

Pattern 1: obfuscated hex string

PATTERN1='0x[0-9a-f]{2000,}'
found_p1="$(grep -rEl "$PATTERN1" node_modules 2>/dev/null || true)"

Pattern 2: base64 string with specific length

PATTERN2='(aHR0cDovLzE5Mi4xNjguNTUuMjE2Ojg4ODgvY29tbWFuZC5waHA/Y29tbWFuZD0=)'
found_p2="$(grep -rEl "$PATTERN2" node_modules 2>/dev/null || true)"
if [[ -n "$found_p1" || -n "$found_p2" ]]; then
found_any=true
echo "[!] Malware patterns found:"
[[ -n "$found_p1" ]] && echo "Pattern 1 (long hex):" && echo "$found_p1"
[[ -n "$found_p2" ]] && echo "Pattern 2 (base64 cmd):" && echo "$found_p2"
else
echo "[] No malware patterns found."
fi
rm -rf "$TMPDIR"
if [[ "$found_any" == "true" ]]; then
echo "[!] Found compromised deps or malware patterns. Remove node_modules/, reinstall, or update to safe versions."
exit 1
else
echo "[
] No issues found."
fi

To use it, navigate to your project directory, save the script (say, as check-deps.sh), make it executable with chmod +x check-deps.sh, and run ./check-deps.sh. It requires jq to be installed and works with npm or bun. If it flags anything, nuke your node_modules and reinstall from a clean lockfile.

Why This Matters for Meme Token Creators

Meme tokens thrive on hype and speed, but that often means cobbling together code from open-source libraries. Tools like Solana's SPL token creator or Ethereum frontends frequently depend on these NPM packages for UI elements or logging. A compromised dependency could turn your fun pump into a rug pull nightmare, draining dev wallets or user funds mid-launch.

This incident underscores the risks in supply chain security—a hot topic in web3, where attacks like the Ledger Connect Kit hack have shown how one weak link can cascade. For blockchain practitioners, adopting practices like using npm ci for consistent installs, pinning versions in package-lock.json, and scanning with tools like Snyk or this script can make all the difference.

Stay vigilant, folks. In the meme token game, knowledge is your best defense against the bears—and the hackers. If you're building something cool, drop a comment on Edgar's thread or share your security tips with the community.

You might be interested