Skip to content

Instantly share code, notes, and snippets.

@fielding
Last active April 1, 2026 01:23
Show Gist options
  • Select an option

  • Save fielding/846f963ae19cb69bc8ae35d9a76ada93 to your computer and use it in GitHub Desktop.

Select an option

Save fielding/846f963ae19cb69bc8ae35d9a76ada93 to your computer and use it in GitHub Desktop.
March 2026 supply chain scanner

Supply Chain Exposure Checker

March 2026 supply chain scanner. Checks for compromised litellm, axios, and plain-crypto-js. Works on macOS, Linux, and Windows (WSL2).

Usage

  1. Read the script first. Or have Claude or your favorite LLM read it for you. The important part is that you inspect it somehow before you run it. This part is not optional.
  2. Once you trust it, you may now enjoy convenience without completely abandoning self-preservation. Or well... you can make the choice with your eyes open if nothing else!
  3. Do not blindly copy-paste the command below from this page like a raccoon with root access.
  4. If you insist on yolo mode, copy the raw URL of the exact script you already reviewed, then reconstruct the command below so there is less room for funny business.

responsible adult instructions

curl -sL 'PASTE_THE_RAW_URL_YOU_ALREADY_REVIEWED_HERE' -o check.sh
less check.sh
bash check.sh

YOLO

curl -sL https://gist.githubusercontent.com/fielding/886a6be1118fdb857a7020f42c32bcd2/raw/dont-pipe-to-bash.sh | bash
#!/usr/bin/env bash
# check.sh - Supply Chain Exposure Checker
# Checks for known-bad versions of litellm, axios, and plain-crypto-js
# If no directories are given, scans $HOME.
#
# These are two separate incidents:
# LiteLLM: March 24, 2026 — compromised PyPI releases 1.82.7 and 1.82.8
# Axios: March 31, 2026 — compromised npm releases 1.14.1 and 0.30.4
# with malicious dep plain-crypto-js@4.2.1
#
# Usage: bash check.sh [directory ...]
set -euo pipefail
# --- incident metadata ------------------------------------------------------
LITELLM_BAD="1.82.7|1.82.8"
AXIOS_BAD="1.14.1|0.30.4"
CRYPTO_BAD="plain-crypto-js"
LITELLM_WINDOW="March 24, 2026 10:39-16:00 UTC"
AXIOS_WINDOW="March 31, 2026 00:21-03:20 UTC"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BOLD='\033[1m'
RESET='\033[0m'
found_count=0
warn() { echo -e "${RED}[!]${RESET} $*"; found_count=$((found_count + 1)); }
ok() { echo -e "${GREEN}[✓]${RESET} $*"; }
info() { echo -e "${YELLOW}[·]${RESET} $*"; }
echo ""
echo -e "${BOLD}Supply Chain Exposure Checker${RESET}"
echo "LiteLLM window: ${LITELLM_WINDOW}"
echo "Axios window: ${AXIOS_WINDOW}"
echo "Checks for:"
echo " - litellm 1.82.7 / 1.82.8"
echo " - axios 1.14.1 / 0.30.4"
echo " - plain-crypto-js 4.2.1"
echo ""
SCAN_DIRS=("${@:-$HOME}")
# --- helpers ----------------------------------------------------------------
find_multi() {
local args=()
local first=true
for pattern in "$@"; do
if [ "$first" = true ]; then
first=false
else
args+=("-o")
fi
args+=("-name" "$pattern")
done
find "${SCAN_DIRS[@]}" -maxdepth 8 \
-not -path "*/.git/*" \
-not -path "*/.Trash/*" \
-not -path "*/node_modules/.cache/*" \
\( "${args[@]}" \) 2>/dev/null
}
# =============================================================================
# LITELLM (PyPI compromise — March 24, 2026)
# =============================================================================
echo -e "${BOLD}--- LiteLLM (PyPI) ---${RESET}"
litellm_found=false
# --- 1a. requirements.txt / constraints.txt (plain line-based formats) ------
info "Scanning requirements/constraints files..."
while IFS= read -r f; do
[ -z "$f" ] && continue
# Pinned to bad version
if grep -qiE '^[[:space:]]*litellm([[:space:]]*\[[^]]+\])?[[:space:]]*(==|~=|>=|<=|>|<)?[[:space:]]*('"${LITELLM_BAD}"')\b' "$f" 2>/dev/null; then
warn "Bad litellm version pinned in: $f"
grep -niE 'litellm' "$f" 2>/dev/null | head -3
litellm_found=true
# Unpinned (bare litellm with no version specifier)
elif grep -qiE '^[[:space:]]*litellm[[:space:]]*$' "$f" 2>/dev/null; then
info "litellm with NO version pin in: $f (could have resolved to bad version)"
litellm_found=true
fi
done < <(find_multi "requirements*.txt" "constraints*.txt")
# --- 1b. pyproject.toml ----------------------------------------------------
info "Scanning pyproject.toml files..."
while IFS= read -r f; do
[ -z "$f" ] && continue
# Matches: litellm = "1.82.8", litellm = "^1.82.8", litellm = {version = "~1.82.7"}, "litellm>=1.82.7"
if grep -qiE 'litellm[[:space:]]*=[[:space:]]*("([^"]*'"1\.82\.(7|8)"'[^"]*)"|\{[^}]*version[[:space:]]*=[[:space:]]*"([^"]*'"1\.82\.(7|8)"'[^"]*)")' "$f" 2>/dev/null; then
warn "Bad litellm version in: $f"
grep -niE 'litellm' "$f" 2>/dev/null | head -3
litellm_found=true
elif grep -qiE '"litellm[><=~!]*1\.82\.(7|8)' "$f" 2>/dev/null; then
warn "Bad litellm version in dependency string: $f"
grep -niE 'litellm' "$f" 2>/dev/null | head -3
litellm_found=true
fi
done < <(find_multi "pyproject.toml")
# --- 1c. poetry.lock / pdm.lock -------------------------------------------
info "Scanning poetry/pdm lockfiles..."
while IFS= read -r f; do
[ -z "$f" ] && continue
awk '
/name = "litellm"/ { in_pkg=1; next }
in_pkg && /version = "1\.82\.(7|8)"/ { found=1 }
/^\[\[/ { in_pkg=0 }
END { exit(found ? 0 : 1) }
' "$f" 2>/dev/null && {
warn "Bad litellm version locked in: $f"
litellm_found=true
}
done < <(find_multi "poetry.lock" "pdm.lock")
# --- 1d. uv.lock ----------------------------------------------------------
info "Scanning uv lockfiles..."
while IFS= read -r f; do
[ -z "$f" ] && continue
awk '
/name = "litellm"/ { in_pkg=1; next }
in_pkg && /version = "1\.82\.(7|8)"/ { found=1 }
/^\[\[/ { in_pkg=0 }
END { exit(found ? 0 : 1) }
' "$f" 2>/dev/null && {
warn "Bad litellm version locked in: $f"
litellm_found=true
}
done < <(find_multi "uv.lock")
# --- 1e. Pipfile.lock (JSON) -----------------------------------------------
info "Scanning Pipfile.lock files..."
while IFS= read -r f; do
[ -z "$f" ] && continue
# JSON structure: "litellm": { "version": "==1.82.7" }
if grep -qE '"litellm"' "$f" 2>/dev/null; then
if grep -A5 '"litellm"' "$f" 2>/dev/null | grep -qE '"version"[[:space:]]*:[[:space:]]*"==('"${LITELLM_BAD}"')"'; then
warn "Bad litellm version in: $f"
litellm_found=true
fi
fi
done < <(find_multi "Pipfile.lock")
# --- 1f. Pipfile / setup.cfg / setup.py ------------------------------------
info "Scanning Pipfile/setup files..."
while IFS= read -r f; do
[ -z "$f" ] && continue
if grep -qiE 'litellm.*('"${LITELLM_BAD}"')' "$f" 2>/dev/null; then
warn "Bad litellm version in: $f"
grep -niE 'litellm' "$f" 2>/dev/null | head -3
litellm_found=true
fi
done < <(find_multi "Pipfile" "setup.cfg" "setup.py")
# --- 2. Installed litellm in venvs / site-packages --------------------------
info "Scanning Python environments for installed litellm..."
while IFS= read -r meta; do
[ -z "$meta" ] && continue
version=$(grep -i "^Version:" "$meta" 2>/dev/null | head -1 | awk '{print $2}')
if echo "$version" | grep -qE "^(${LITELLM_BAD})$"; then
warn "BAD litellm ${version} installed at: $meta"
litellm_found=true
elif [ -n "$version" ]; then
ok "litellm ${version} installed at: $(dirname "$meta") (safe version)"
fi
done < <(find "${SCAN_DIRS[@]}" -maxdepth 8 -path "*/site-packages/litellm-*.dist-info/METADATA" 2>/dev/null || true)
# Active python environments
for pip_cmd in pip pip3 "python3 -m pip" "python -m pip"; do
version=$($pip_cmd show litellm 2>/dev/null | grep -i "^Version:" | awk '{print $2}' || true)
if echo "$version" | grep -qE "^(${LITELLM_BAD})$"; then
warn "BAD litellm ${version} installed in active env ($pip_cmd)"
litellm_found=true
elif [ -n "$version" ]; then
ok "litellm ${version} in active env ($pip_cmd) -- safe"
fi
done
# --- 3. Dockerfiles and shell scripts with unpinned litellm installs --------
info "Scanning Dockerfiles and scripts for unpinned litellm installs..."
SELF_PATH="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
while IFS= read -r f; do
[ -z "$f" ] && continue
# Skip self
[ "$(cd "$(dirname "$f")" && pwd)/$(basename "$f")" = "$SELF_PATH" ] 2>/dev/null && continue
# pip install litellm without version pin
if grep -nE '\bpip3?\s+install\b' "$f" 2>/dev/null | grep -iE '\blitellm\b' | grep -qvE '(==|~=|>=|<=|>|<)[[:space:]]*[0-9]'; then
info "Unpinned 'pip install litellm' in: $f (check if built during ${LITELLM_WINDOW})"
grep -nE '\bpip3?\s+install\b.*\blitellm\b' "$f" 2>/dev/null | head -3
litellm_found=true
fi
# uv pip install litellm without version pin
if grep -nE '\buv\s+pip\s+install\b' "$f" 2>/dev/null | grep -iE '\blitellm\b' | grep -qvE '(==|~=|>=|<=|>|<)[[:space:]]*[0-9]'; then
info "Unpinned 'uv pip install litellm' in: $f (check if built during ${LITELLM_WINDOW})"
litellm_found=true
fi
done < <(find_multi "Dockerfile" "Dockerfile.*" "*.sh" "*.bash" ".gitlab-ci.yml" ".github" "Makefile")
if [ "$litellm_found" = false ]; then
ok "No litellm exposure found"
fi
# =============================================================================
# AXIOS / plain-crypto-js (npm compromise — March 31, 2026)
# =============================================================================
echo ""
echo -e "${BOLD}--- Axios / plain-crypto-js (npm) ---${RESET}"
axios_found=false
# --- 4a. JS lockfiles for bad axios versions --------------------------------
info "Scanning JS lockfiles for axios ${AXIOS_BAD//|/ / }..."
while IFS= read -r f; do
[ -z "$f" ] && continue
if grep -qE "axios[@/\"': ]*(${AXIOS_BAD})" "$f" 2>/dev/null; then
warn "Bad axios version in lockfile: $f"
grep -nE "axios.*(${AXIOS_BAD})" "$f" 2>/dev/null | head -5
axios_found=true
fi
done < <(find_multi "package-lock.json" "yarn.lock" "pnpm-lock.yaml" "npm-shrinkwrap.json")
# --- 4b. node_modules for bad axios ----------------------------------------
while IFS= read -r pkg; do
[ -z "$pkg" ] && continue
version=$(grep -o '"version": *"[^"]*"' "$pkg" 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
if echo "$version" | grep -qE "^(${AXIOS_BAD})$"; then
warn "BAD axios ${version} installed at: $(dirname "$pkg")"
axios_found=true
fi
done < <(find "${SCAN_DIRS[@]}" -maxdepth 8 -path "*/node_modules/axios/package.json" 2>/dev/null || true)
if [ "$axios_found" = false ]; then
ok "No bad axios versions found in lockfiles or node_modules"
fi
# --- 4c. plain-crypto-js (the malicious dependency) ------------------------
echo ""
info "Scanning for plain-crypto-js (malicious axios dependency)..."
crypto_found=false
while IFS= read -r f; do
[ -z "$f" ] && continue
if grep -qlE "${CRYPTO_BAD}(@|[^0-9]*(4\.2\.1))?" "$f" 2>/dev/null; then
warn "plain-crypto-js referenced in: $f"
grep -nE "${CRYPTO_BAD}" "$f" 2>/dev/null | head -3
crypto_found=true
fi
done < <(find_multi \
"package-lock.json" "yarn.lock" "pnpm-lock.yaml" "npm-shrinkwrap.json" "package.json")
# Check node_modules
if find "${SCAN_DIRS[@]}" -maxdepth 8 -path "*/node_modules/plain-crypto-js" -print -quit 2>/dev/null | grep -q .; then
warn "plain-crypto-js installed in node_modules!"
crypto_found=true
fi
if [ "$crypto_found" = false ]; then
ok "No plain-crypto-js found"
fi
# --- 4d. Filesystem IOCs from axios payload ---------------------------------
echo ""
info "Scanning for axios compromise filesystem artifacts (IOCs)..."
ioc_found=false
# macOS
if [ -e "/Library/Caches/com.apple.act.mond" ]; then
warn "Axios IOC found: /Library/Caches/com.apple.act.mond"
ioc_found=true
fi
# Linux
if [ -e "/tmp/ld.py" ]; then
warn "Axios IOC found: /tmp/ld.py"
ioc_found=true
fi
# WSL / Windows-mounted paths (best effort)
for p in \
"/mnt/c/ProgramData/wt.exe" \
"/mnt/c/Windows/Temp/6202033.vbs" \
"/mnt/c/Windows/Temp/6202033.ps1"; do
if [ -e "$p" ]; then
warn "Axios IOC found: $p"
ioc_found=true
fi
done
if [ "$ioc_found" = false ]; then
ok "No known axios IOC artifacts found"
else
warn "IOC artifacts detected -- this machine may have executed the malicious payload"
echo " Treat this machine as compromised. Rotate all secrets and rebuild from clean image."
fi
# =============================================================================
# PACKAGE MANAGER CACHES
# =============================================================================
echo ""
echo -e "${BOLD}--- Package manager caches ---${RESET}"
cache_hit=false
# pip cache
for cache_dir in \
"${HOME}/Library/Caches/pip" \
"${HOME}/.cache/pip" \
"${XDG_CACHE_HOME:-${HOME}/.cache}/pip"; do
if [ -d "$cache_dir" ]; then
if find "$cache_dir" \( -name "litellm-1.82.7*" -o -name "litellm-1.82.8*" \) -print -quit 2>/dev/null | grep -q .; then
warn "Cached bad litellm wheel/sdist in: $cache_dir"
cache_hit=true
fi
fi
done
# uv cache
for cache_dir in \
"${HOME}/Library/Caches/uv" \
"${HOME}/.cache/uv" \
"${XDG_CACHE_HOME:-${HOME}/.cache}/uv"; do
if [ -d "$cache_dir" ]; then
if find "$cache_dir" \( -name "litellm-1.82.7*" -o -name "litellm-1.82.8*" \) -print -quit 2>/dev/null | grep -q .; then
warn "Cached bad litellm in uv cache: $cache_dir"
cache_hit=true
fi
fi
done
# npm cache
for cache_dir in \
"${HOME}/.npm" \
"${HOME}/AppData/Local/npm-cache" \
"${XDG_CACHE_HOME:-${HOME}/.cache}/npm"; do
if [ -d "$cache_dir" ]; then
if find "$cache_dir" \( \
-iname '*axios*1.14.1*' -o \
-iname '*axios*0.30.4*' -o \
-iname '*plain-crypto-js*' \
\) -print -quit 2>/dev/null | grep -q .; then
warn "Suspicious npm cache entry in: $cache_dir"
cache_hit=true
fi
fi
done
if [ "$cache_hit" = false ]; then
ok "Package manager caches clean (pip, uv, npm)"
fi
# =============================================================================
# DOCKER IMAGES
# =============================================================================
echo ""
echo -e "${BOLD}--- Docker images ---${RESET}"
if command -v docker &>/dev/null && docker info &>/dev/null 2>&1; then
info "Scanning Docker images for compromised packages..."
docker_hit=false
while IFS= read -r image; do
[ -z "$image" ] && continue
# Check litellm (Python)
result=$(docker run --rm --entrypoint="" "$image" \
pip show litellm 2>/dev/null | grep -i "^Version:" | awk '{print $2}' || true) 2>/dev/null
if echo "$result" | grep -qE "^(${LITELLM_BAD})$"; then
warn "Docker image ${image} has BAD litellm ${result}"
docker_hit=true
fi
# Check axios / plain-crypto-js (Node)
node_result=$(docker run --rm --entrypoint="" "$image" sh -c '
for f in $(find / -path "*/node_modules/axios/package.json" -o -path "*/node_modules/plain-crypto-js/package.json" 2>/dev/null | head -10); do
echo "$f: $(grep -o "\"version\": *\"[^\"]*\"" "$f" 2>/dev/null | head -1)"
done
' 2>/dev/null || true)
if echo "$node_result" | grep -qE "axios.*\"(${AXIOS_BAD})\""; then
warn "Docker image ${image} has BAD axios: $node_result"
docker_hit=true
fi
if echo "$node_result" | grep -q "plain-crypto-js"; then
warn "Docker image ${image} has plain-crypto-js: $node_result"
docker_hit=true
fi
done < <(docker images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -v '<none>' | head -20)
if [ "$docker_hit" = false ]; then
ok "No compromised packages in local Docker images (checked up to 20)"
fi
else
info "Docker not available or not running -- skipping image scan"
fi
# =============================================================================
# SUMMARY
# =============================================================================
echo ""
echo "============================================="
if [ "$found_count" -gt 0 ]; then
echo -e "${RED}${BOLD}FOUND ${found_count} ISSUE(S)${RESET}"
echo ""
echo "Recommended actions:"
echo " 1. Rotate ALL secrets accessible to affected machines/envs"
echo " (cloud creds, API keys, SSH keys, GitHub tokens, DB passwords)"
echo " 2. Rebuild affected containers/envs from clean base images"
echo " 3. Update to safe versions:"
echo " - litellm >= 1.83.0"
echo " - axios: pin to 1.14.0 (1.x) or 0.30.3 (0.x), then upgrade after verifying upstream"
echo " 4. Clear package caches: pip cache purge / npm cache clean --force"
echo " 5. Check CI/CD build logs for:"
echo " - LiteLLM: pip installs on March 24, 2026"
echo " - Axios: npm/yarn/pnpm installs on March 31, 2026"
echo " - Look for: axios@1.14.1, axios@0.30.4, plain-crypto-js@4.2.1, postinstall execution"
echo ""
echo "References:"
echo " https://docs.litellm.ai/blog/security-update-march-2026"
echo " https://socket.dev/blog/axios-npm-compromise"
exit 1
else
echo -e "${GREEN}${BOLD}NO KNOWN MATCHES FOUND IN SCANNED PATHS${RESET}"
echo ""
echo -e "${BOLD}This is a local best-effort scan, not a forensic guarantee.${RESET}"
echo "The axios payload may self-delete after execution -- a clean"
echo "filesystem does not prove the system was never exposed."
echo ""
echo "This scan checked:"
echo " * Python deps (requirements, pyproject.toml, lockfiles, venvs, active env)"
echo " * Dockerfiles and scripts for unpinned litellm installs"
echo " * JS deps (lockfiles, node_modules, npm-shrinkwrap)"
echo " * Axios filesystem IOCs (macOS, Linux, Windows/WSL)"
echo " * Package manager caches (pip, uv, npm)"
echo " * Docker images (Python and Node)"
echo ""
echo "This does NOT check:"
echo " * Remote CI/CD (GitHub Actions, GitLab CI, etc.)"
echo " * Cloud deploy hosts (Render, Vercel, AWS, etc.)"
echo " * bun.lockb (binary format, not grep-able)"
echo ""
echo "Review build logs for:"
echo " LiteLLM: pip install runs on March 24, 2026"
echo " Axios: npm/yarn/pnpm installs on March 31, 2026"
echo " look for axios@1.14.1, axios@0.30.4,"
echo " plain-crypto-js@4.2.1, postinstall execution"
echo ""
echo "Run this script on each machine and check build logs separately."
exit 0
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment