Skip to content

Instantly share code, notes, and snippets.

@gabrielstuff
Last active May 15, 2026 09:31
Show Gist options
  • Select an option

  • Save gabrielstuff/1ec8cb4a351745895c76dfadc789090a to your computer and use it in GitHub Desktop.

Select an option

Save gabrielstuff/1ec8cb4a351745895c76dfadc789090a to your computer and use it in GitHub Desktop.
A simple check for tanstack hack
#!/usr/bin/env bash
# Mini Shai-Hulud / TanStack npm supply-chain compromise check.
# Safe to run — read-only, no network calls, no modifications.
# Works in bash and zsh. macOS + Linux.
#
# Based on IOCs published by Socket.dev (2026-05-11) and OpenAI's response
# (2026-05-13). Covers TanStack, Mistral AI, OpenSearch, Guardrails AI,
# Squawk, and the gh-token-monitor stealer drop.
set +e
set -u
# ---------- colors ----------
RED=$'\033[31m'; GRN=$'\033[32m'; YLW=$'\033[33m'; BLD=$'\033[1m'; DIM=$'\033[2m'; RST=$'\033[0m'
[ -t 1 ] || { RED=""; GRN=""; YLW=""; BLD=""; DIM=""; RST=""; }
HITS=0
FINDINGS=()
section() { printf '\n%s── %s ──%s\n' "$BLD" "$1" "$RST"; }
ok() { printf ' %sok%s %s\n' "$GRN" "$RST" "$1"; }
bad() { HITS=$((HITS + 1)); FINDINGS+=("$1"); printf ' %sHIT%s %s\n' "$RED" "$RST" "$1"; }
note() { printf ' %s%s%s\n' "$DIM" "$1" "$RST"; }
# Known malicious SHA-256s (Socket.dev + Mistral AI advisory)
SHA_ROUTER_INIT="ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c"
SHA_TANSTACK_RUNNER="2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96"
SHA_TANSTACK_SETUP_PKG="7c12d8614c624c70d6dd6fc2ee289332474abaa38f70ebe2cdef064923ca3a9b"
# Compromised commits in TanStack/router fork network
BAD_COMMIT="79ac49eedf774dd4b0cfa308722bc463cfe5885c"
BAD_COMMIT_FORK="65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14"
# Known C2 / attacker infra (domains + IP)
BAD_HOSTS='filev2\.getsession\.org|seed[123]\.getsession\.org|git-tanstack\.com|api\.masscan\.cloud|litter\.catbox\.moe|83\.142\.209\.194'
# Attacker GitHub identities
BAD_GH_USERS='zblgg|voicproducoes|zblgg/configuration'
# Compromised npm/PyPI artifacts (specific versions only)
# Format: name@version, one per line
BAD_PKGS=$(cat <<'EOF'
@mistralai/mistralai@2.2.2
@mistralai/mistralai@2.2.3
@mistralai/mistralai@2.2.4
@mistralai/mistralai-azure@1.7.1
@mistralai/mistralai-azure@1.7.2
@mistralai/mistralai-azure@1.7.3
@mistralai/mistralai-gcp@1.7.1
@mistralai/mistralai-gcp@1.7.2
@mistralai/mistralai-gcp@1.7.3
@opensearch-project/opensearch@3.5.3
@opensearch-project/opensearch@3.6.2
@opensearch-project/opensearch@3.7.0
@opensearch-project/opensearch@3.8.0
@squawk/mcp@0.9.2
@squawk/mcp@0.9.3
@squawk/mcp@0.9.4
@squawk/mcp@0.9.5
@squawk/weather@0.5.10
@squawk/flightplan@0.5.6
guardrails-ai@0.10.1
mistralai@2.4.6
cross-stitch@1.1.4
cross-stitch@1.1.5
cross-stitch@1.1.6
cross-stitch@1.1.7
ts-dna@3.0.3
ts-dna@3.0.4
ts-dna@3.0.5
git-git-git@1.0.10
git-git-git@1.0.11
git-git-git@1.0.12
git-branch-selector@1.3.6
git-branch-selector@1.3.7
nextmove-mcp@0.1.5
nextmove-mcp@0.1.6
nextmove-mcp@0.1.7
cmux-agent-mcp@0.1.7
cmux-agent-mcp@0.1.8
EOF
)
check_path() {
local label="$1" p="$2"
if [ -e "$p" ] || [ -L "$p" ]; then
bad "$label exists → $p"
ls -la "$p" 2>/dev/null | sed 's/^/ /'
else
ok "$label not present"
fi
}
# pick a sha256 tool
SHA=""
command -v shasum >/dev/null 2>&1 && SHA="shasum -a 256"
[ -z "$SHA" ] && command -v sha256sum >/dev/null 2>&1 && SHA="sha256sum"
# ---------- header ----------
cat <<EOF
${BLD}Mini Shai-Hulud / TanStack supply-chain compromise check${RST}
${DIM}Scans for IOCs from the npm attack publicly disclosed 2026-05-11.
Read-only. No network calls. Will NOT flag legitimate apps.${RST}
host: $(hostname 2>/dev/null)
user: $(id -un 2>/dev/null)
os: $(uname -srm 2>/dev/null)
shell: ${SHELL:-unknown}
date: $(date -u '+%Y-%m-%dT%H:%M:%SZ')
EOF
# ---------- 1. malicious payload files anywhere (incl. node_modules) ----------
section "1. Payload files (router_init.js, tanstack_runner.js, vite_setup.mjs)"
note "IOC files dropped into compromised TanStack/Mistral/etc. packages"
note "scanning node_modules — that's where the files live"
RI_LIST=$(find "$HOME" \
\( -name .Trash -o -name Library \) -prune \
-o -type f \( -name router_init.js -o -name tanstack_runner.js -o -name vite_setup.mjs \) -print 2>/dev/null)
if [ -n "$RI_LIST" ]; then
bad "malicious payload file(s) found"
while IFS= read -r f; do
printf ' %s\n' "$f"
if [ -n "$SHA" ]; then
h=$($SHA "$f" 2>/dev/null | awk '{print $1}')
case "$h" in
"$SHA_ROUTER_INIT"|"$SHA_TANSTACK_RUNNER")
printf ' %s↳ SHA-256 MATCHES KNOWN MALWARE: %s%s\n' "$RED" "$h" "$RST" ;;
*)
printf ' %s↳ sha256: %s%s\n' "$DIM" "$h" "$RST" ;;
esac
fi
done <<< "$RI_LIST"
else
ok "no router_init.js / tanstack_runner.js / vite_setup.mjs found"
fi
# ---------- 2. gh-token-monitor stealer persistence ----------
section "2. gh-token-monitor stealer persistence"
check_path "launch script " "$HOME/.local/bin/gh-token-monitor.sh"
check_path "systemd unit " "$HOME/.config/systemd/user/gh-token-monitor.service"
check_path "macOS LaunchAgent" "$HOME/Library/LaunchAgents/com.user.gh-token-monitor.plist"
# ---------- 3. agent / editor config dir drops ----------
section "3. Suspicious scripts in agent + editor config dirs"
note "covers .claude .codex .opencode .cursor .windsurf .aider .continue .zed .trae .vscode"
AGENT_DIRS='.claude|.codex|.opencode|.cursor|.windsurf|.aider|.continue|.zed|.trae|.vscode|.copilot|.github-copilot'
DROP_HITS=$(find "$HOME" \
\( -name node_modules -o -name extensions -o -name .Trash -o -name Library \) -prune \
-o -type f \( -name 'setup.mjs' -o -name 'router_runtime.js' -o -name 'router_init.js' -o -name 'tanstack_runner.js' \) \
-print 2>/dev/null \
| grep -E "/($AGENT_DIRS)/")
if [ -n "$DROP_HITS" ]; then
bad "suspicious script(s) in agent/editor config dirs"
printf '%s\n' "$DROP_HITS" | sed 's/^/ /'
else
ok "no suspicious scripts in agent/editor config dirs"
fi
# ---------- 4. .claude/settings.json hooks ----------
section "4. .claude/settings.json with unexpected hooks"
SETTINGS_HITS=""
while IFS= read -r f; do
case "$f" in */node_modules/*) continue ;; esac
if grep -Ei "router_init|router_runtime|tanstack_runner|vite_setup|setup\.mjs|$BAD_HOSTS" "$f" >/dev/null 2>&1; then
SETTINGS_HITS="${SETTINGS_HITS}${f}"$'\n'
fi
done < <(find "$HOME" \
\( -name node_modules -o -name .Trash -o -name Library \) -prune \
-o -path '*/.claude/settings.json' -type f -print 2>/dev/null)
if [ -n "$SETTINGS_HITS" ]; then
bad ".claude/settings.json contains IOC strings"
printf '%s' "$SETTINGS_HITS" | sed 's/^/ /'
else
ok ".claude/settings.json files clean"
fi
# ---------- 5. .vscode/tasks.json content ----------
section "5. .vscode/tasks.json containing payload"
note "tasks.json is normal — only flagged if it contains IOC strings"
TJ_HITS=""
while IFS= read -r f; do
case "$f" in */node_modules/*|*/extensions/*) continue ;; esac
if grep -lEi "gh-token-monitor|router_init|router_runtime|tanstack_runner|vite_setup|setup\.mjs|$BAD_HOSTS|curl .*\| *(sh|bash|node)|wget .*\| *(sh|bash|node)" "$f" >/dev/null 2>&1; then
TJ_HITS="${TJ_HITS}${f}"$'\n'
fi
done < <(find "$HOME" \
\( -name node_modules -o -name extensions -o -name .Trash -o -name Library \) -prune \
-o -path '*/.vscode/tasks.json' -type f -print 2>/dev/null)
if [ -n "$TJ_HITS" ]; then
bad "tasks.json with malware payload"
printf '%s' "$TJ_HITS" | sed 's/^/ /'
else
ok "no tasks.json contains payload strings"
fi
# ---------- 6. /tmp/transformers.pyz (guardrails-ai variant) ----------
section "6. /tmp/transformers.pyz (guardrails-ai payload)"
if [ -e /tmp/transformers.pyz ]; then
bad "/tmp/transformers.pyz exists"
ls -la /tmp/transformers.pyz 2>/dev/null | sed 's/^/ /'
else
ok "/tmp/transformers.pyz not present"
fi
# ---------- 7. compromised packages in lockfiles ----------
section "7. Compromised package versions in lockfiles"
note "scans package-lock.json / pnpm-lock.yaml / yarn.lock / package.json / requirements.txt"
LOCK_HITS=""
LOCK_FILES=$(find "$HOME" \
\( -name node_modules -o -name .Trash -o -name Library -o -name .git \) -prune \
-o -type f \( -name 'package-lock.json' -o -name 'pnpm-lock.yaml' -o -name 'yarn.lock' -o -name 'package.json' -o -name 'requirements.txt' -o -name 'poetry.lock' -o -name 'uv.lock' \) -print 2>/dev/null)
while IFS= read -r pv; do
[ -z "$pv" ] && continue
name="${pv%@*}"
ver="${pv##*@}"
# Build a tolerant pattern. e.g. "@tanstack/react-router" at "1.2.3"
# Match common lockfile encodings.
while IFS= read -r lf; do
[ -z "$lf" ] && continue
if grep -F -e "\"${name}\": \"${ver}\"" \
-e "\"${name}@${ver}\"" \
-e "${name}@${ver}:" \
-e "${name}==${ver}" \
-e "name = \"${name}\"" "$lf" >/dev/null 2>&1; then
# second pass: if matched name= alone (poetry/uv), verify version proximity
if grep -F -e "\"${name}\": \"${ver}\"" -e "\"${name}@${ver}\"" -e "${name}@${ver}:" -e "${name}==${ver}" "$lf" >/dev/null 2>&1 \
|| awk -v n="$name" -v v="$ver" '
/^\[\[package\]\]/ {pkg=""; ver=""}
$1=="name" {gsub(/"/,"",$3); pkg=$3}
$1=="version" {gsub(/"/,"",$3); ver=$3}
pkg==n && ver==v {found=1; exit}
END{exit !found}
' "$lf" 2>/dev/null; then
LOCK_HITS="${LOCK_HITS}${pv} ← ${lf}"$'\n'
fi
fi
done <<< "$LOCK_FILES"
done <<< "$BAD_PKGS"
if [ -n "$LOCK_HITS" ]; then
bad "compromised package version(s) installed/locked"
printf '%s' "$LOCK_HITS" | sed 's/^/ /'
else
ok "no compromised package versions found in lockfiles"
fi
# ---------- 8. malicious optionalDependencies / commit hash ----------
section "8. package.json with attacker optionalDependencies or commit hash"
OPT_HITS=""
while IFS= read -r pj; do
if grep -F -e "$BAD_COMMIT" -e "$BAD_COMMIT_FORK" -e '@tanstack/setup' -e 'github:tanstack/router#79ac49ee' -e 'zblgg/configuration' "$pj" >/dev/null 2>&1; then
OPT_HITS="${OPT_HITS}${pj}"$'\n'
fi
done < <(find "$HOME" \
\( -name .Trash -o -name Library \) -prune \
-o -type f -name 'package.json' -print 2>/dev/null)
if [ -n "$OPT_HITS" ]; then
bad "package.json references attacker commit / @tanstack/setup"
printf '%s' "$OPT_HITS" | sed 's/^/ /'
else
ok "no package.json references attacker commit"
fi
# ---------- 9. running processes ----------
section "9. Running processes"
PROC_HITS=$(ps -Aewwo pid,user,command 2>/dev/null \
| grep -Ei 'gh-token-monitor|router_init|router_runtime|tanstack_runner|vite_setup|\.claude/setup\.mjs|\.vscode/setup\.mjs|transformers\.pyz|MISTRAL_INIT=1' \
| grep -v grep)
if [ -n "$PROC_HITS" ]; then
bad "suspicious process(es) running"
printf '%s\n' "$PROC_HITS" | sed 's/^/ /'
else
ok "no suspicious processes running"
fi
# ---------- 10. LaunchAgents/Daemons (macOS) ----------
if [ "$(uname)" = "Darwin" ]; then
section "10. macOS LaunchAgents/Daemons referencing malware"
LA_HITS=$(find \
"$HOME/Library/LaunchAgents" \
"/Library/LaunchAgents" \
"/Library/LaunchDaemons" \
-type f -name '*.plist' 2>/dev/null \
| xargs grep -lEi "gh-token-monitor|router_init|router_runtime|tanstack_runner|vite_setup|$BAD_HOSTS" 2>/dev/null)
if [ -n "$LA_HITS" ]; then
bad "LaunchAgent/Daemon references malware"
printf '%s\n' "$LA_HITS" | sed 's/^/ /'
else
ok "no LaunchAgent/Daemon references malware"
fi
fi
# ---------- 11. shell rc + cron ----------
section "11. Shell startup files + crontab"
RC_FILES=("$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.zshrc" "$HOME/.zprofile" "$HOME/.profile")
RC_HITS=""
for rc in "${RC_FILES[@]}"; do
[ -f "$rc" ] || continue
if grep -Ei "gh-token-monitor|router_init|router_runtime|tanstack_runner|vite_setup|setup\.mjs|$BAD_HOSTS|MISTRAL_INIT" "$rc" >/dev/null 2>&1; then
RC_HITS="${RC_HITS}${rc}"$'\n'
fi
done
if [ -n "$RC_HITS" ]; then
bad "shell startup file modified"
printf '%s' "$RC_HITS" | sed 's/^/ /'
else
ok "shell startup files clean"
fi
CRON_OUT=$(crontab -l 2>/dev/null | grep -Ei "gh-token-monitor|router_init|router_runtime|vite_setup|setup\.mjs|transformers\.pyz|$BAD_HOSTS")
if [ -n "$CRON_OUT" ]; then
bad "crontab contains malware reference"
printf '%s\n' "$CRON_OUT" | sed 's/^/ /'
else
ok "crontab clean"
fi
# ---------- 12. C2 hosts in /etc/hosts ----------
section "12. C2 domains in /etc/hosts"
HOSTS_HITS=$(grep -Ei "$BAD_HOSTS" /etc/hosts 2>/dev/null)
if [ -n "$HOSTS_HITS" ]; then
bad "/etc/hosts mentions C2 domain"
printf '%s\n' "$HOSTS_HITS" | sed 's/^/ /'
else
ok "/etc/hosts does not mention known C2 domains"
fi
# ---------- 13. git commits attributed to claude@users.noreply ----------
# Only spot-check repos under common dev dirs; full-disk scan would be too slow.
section "13. Recent git commits as claude@users.noreply.github.com"
note "looks across ~/Developer ~/Projects ~/Code ~/src ~/repos for repos with such commits"
note "Socket.dev: attacker forged commits under this identity"
GIT_HITS=""
for base in "$HOME/Developer" "$HOME/Projects" "$HOME/Code" "$HOME/src" "$HOME/repos" "$HOME/work"; do
[ -d "$base" ] || continue
while IFS= read -r gd; do
repo="${gd%/.git}"
out=$(git -C "$repo" log --all --since='2026-04-01' --author='claude@users.noreply.github.com' --pretty='%h %ad %s' --date=short 2>/dev/null | head -n 5)
if [ -n "$out" ]; then
GIT_HITS="${GIT_HITS}${repo}:"$'\n'"${out}"$'\n\n'
fi
done < <(find "$base" -maxdepth 4 -type d -name .git 2>/dev/null)
done
if [ -n "$GIT_HITS" ]; then
bad "commits authored as claude@users.noreply.github.com (verify each was made by the legit Claude Code App)"
printf '%s' "$GIT_HITS" | sed 's/^/ /'
else
ok "no recent commits by claude@users.noreply.github.com in scanned repos"
fi
# ---------- 14. git remotes pointing at attacker fork ----------
section "14. Git remotes referencing attacker fork or commits"
REMOTE_HITS=""
for base in "$HOME/Developer" "$HOME/Projects" "$HOME/Code" "$HOME/src" "$HOME/repos" "$HOME/work"; do
[ -d "$base" ] || continue
while IFS= read -r gd; do
repo="${gd%/.git}"
rem=$(git -C "$repo" remote -v 2>/dev/null | grep -Ei "$BAD_GH_USERS")
if [ -n "$rem" ]; then
REMOTE_HITS="${REMOTE_HITS}${repo}:"$'\n'"${rem}"$'\n\n'
fi
# also check if the attacker commits are reachable in any local clone
for c in "$BAD_COMMIT" "$BAD_COMMIT_FORK"; do
if git -C "$repo" cat-file -e "$c^{commit}" 2>/dev/null; then
REMOTE_HITS="${REMOTE_HITS}${repo}: contains attacker commit $c"$'\n\n'
fi
done
done < <(find "$base" -maxdepth 4 -type d -name .git 2>/dev/null)
done
if [ -n "$REMOTE_HITS" ]; then
bad "git remote/commit references attacker account or commit"
printf '%s' "$REMOTE_HITS" | sed 's/^/ /'
else
ok "no git remotes/commits reference attacker accounts"
fi
# ---------- summary ----------
printf '\n%s════ Result ════%s\n' "$BLD" "$RST"
if [ "$HITS" -eq 0 ]; then
cat <<EOF
${GRN}${BLD}CLEAN${RST} — no Mini Shai-Hulud IOCs found on this host.
${DIM}This script covers the publicly disclosed IOCs as of 2026-05-13.
Active campaigns evolve — paste this output into Claude/Codex and ask
for a second opinion if you're unsure.${RST}
verdict: CLEAN
EOF
else
printf '%s%sCOMPROMISE INDICATORS FOUND%s — %d hit(s):\n\n' "$RED" "$BLD" "$RST" "$HITS"
for f in "${FINDINGS[@]}"; do printf ' • %s\n' "$f"; done
cat <<EOF
${YLW}What to do RIGHT NOW:${RST}
1. Disconnect this machine from the network (Wi-Fi off, unplug ethernet).
2. Do NOT type any passwords on this machine.
3. Rotate from a CLEAN device, in priority order:
- npm tokens
- GitHub PATs and OIDC trust grants
- AWS keys + instance roles
- Vault / Kubernetes service-account tokens
- SSH keys + signing keys
4. Block egress to filev2.getsession.org, seed{1,2,3}.getsession.org,
git-tanstack.com, api.masscan.cloud, litter.catbox.moe, and 83.142.209.194.
5. Audit recent GitHub commits and npm publishes from your org.
6. Paste this ENTIRE output into Claude or Codex (from a clean device) and ask:
"Am I compromised by Mini Shai-Hulud / the TanStack npm attack?
Here is my scan output: <paste>"
References:
- https://openai.com/index/our-response-to-the-tanstack-npm-supply-chain-attack/
- https://socket.dev/blog/tanstack-npm-packages-compromised-mini-shai-hulud-supply-chain-attack
- https://tanstack.com/blog/npm-supply-chain-compromise-postmortem
- https://docs.mistral.ai/resources/security-advisories
- GitHub Security Advisory: GHSA-g7cv-rxg3-hmpx (TanStack)
- GitHub Security Advisory: GHSA-jgg6-4rpr-wfh7 (Mistral npm)
- GitHub Security Advisory: GHSA-wx9m-wx4f-4cmg (Mistral PyPI)
verdict: SUSPECT
EOF
fi
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment