ブロックチェーンや meme token 開発のスピード重視の世界では、セキュリティ脅威を常に把握しておくことが不可欠です。2025年9月8日、JavaScript コミュニティは NPM(Node Package Manager)で発生した大規模なサプライチェーン攻撃の一つに衝撃を受けました。攻撃者はフィッシングでコントリビューターのアカウントを奪い、18個の広く使われているパッケージに悪意あるバージョンを公開しました。これらにはウォレットの取引を横取りするよう設計された cryptostealer マルウェアが含まれていました。単なる一般的なウェブ開発の問題ではなく、Bitcoin、Ethereum、Solana といった仮想通貨を標的にし、資金を攻撃者管理のアドレスへ迂回させるため、暗号コミュニティに対する直接的な打撃です。
攻撃の概要
この侵害は、カラフルなコンソールログから ANSI 文字列処理に至るまで、JS エコシステムの基盤となるパッケージに影響しました。Aikido Security と Semgrep のセキュリティ報告によれば、侵害されたバージョンには難読化された JavaScript コードが含まれ、fetch や XMLHttpRequest といったブラウザ機能にフックします。ウォレットとのやり取りをスキャンして正当なアドレスを類似アドレスに置き換え、署名直前にトランザクションパラメータを改変する — しかもユーザーインターフェースは正常に見せかけたまま、という手口でした。
影響を受けたパッケージとその侵害バージョンは以下の通りです:
- [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]
これらのパッケージは週に何十億回もダウンロードされており、無数のプロジェクトの依存ツリーに潜んでいる可能性があります。ブロックチェーン関係者にとって特に憂慮すべき点は、meme token のローンチが迅速に構築されたフロントエンドや DEX インターフェース、あるいは React や Node.js を使ったボットなどに依存することが多い点です。もしあなたの dApp や token sniping script がこれらのユーティリティに依存していると、ユーザーや自分自身の資金が知らず知らずのうちに流出するリスクがあります。
朗報は、コミュニティが迅速に対応したことです。多くのダウンロードが発生する前に悪意あるバージョンは NPM から削除され、被害は限定されました。しかし、最近パッケージをインストールしたなら、今すぐ確認する必要があります。
Edgar Pavlovsky のタイムリーなスクリプト
convexity のエキスパートで Dark Research AI や Paladin Solana などに関わる Edgar Pavlovsky は、反響を呼んだツイートスレッドで、package.json の依存ツリーをスキャンしてこれらの侵害パッケージを検出する bash スクリプトを共有しました。これは既知の悪いバージョンをチェックするだけでなく、node_modules 内の難読化されたマルウェアパターンも検出する実用的なツールです。
Edgar はその後まもなくスクリプトを更新し、巧妙なパターンの検出を強化しました。Gist から入手できます。参照用にスクリプト全文を以下に示します:
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
使い方はプロジェクトディレクトリに移動し、スクリプトを保存(例: check-deps.sh)、chmod +x check-deps.sh
で実行権を与え、./check-deps.sh
を実行するだけです。jq のインストールが必要で、npm か bun と併用します。もし何か検出されたら、node_modules を削除してクリーンな lockfile から再インストールしてください。
meme token クリエイターにとっての重要性
meme token はハイプとスピードで成り立っていますが、その多くはオープンソースライブラリを寄せ集めて作られています。Solana の SPL token creator や Ethereum のフロントエンドなどは、UI 要素やログ出力にこれらの NPM パッケージを頻繁に利用します。依存関係が侵害されていれば、楽しい pump が rug pull の悪夢に変わり、開発者ウォレットやユーザー資金がローンチ中に流出する可能性があります。
この事件はサプライチェーンセキュリティのリスクを改めて示しています。Ledger Connect Kit のハックのように、一つの弱いリンクが連鎖的な被害を引き起こすことがあります。ブロックチェーン実務者は、npm ci
のような一貫したインストール方法の採用、package-lock.json でのバージョン固定、Snyk や今回のスクリプトのようなツールによるスキャンといった対策を取ることで、被害を大きく減らせます。
油断せずに。meme token の世界では、知識こそがベア(下落相場)やハッカーに対する最大の防御です。何か面白いものを作っているなら、Edgar のスレッドにコメントを残すか、コミュニティとセキュリティの知見を共有してください。