Created
March 26, 2026 14:16
-
-
Save albertorb/b2991be56e616fe0379907f6474e7e62 to your computer and use it in GitHub Desktop.
Helper script to check if you have been compromised due to litellm 1.82.7 and 1.82.8.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -u | |
| # ===== CONFIG ===== | |
| TARGET_VERSIONS=("1.82.7" "1.82.8") | |
| TARGET_VERSIONS_REGEX='1\.(82\.7|82\.8)' | |
| TARGET_VERSIONS_LABEL="1.82.7 / 1.82.8" | |
| REPO_PATH="${1:-}" | |
| if [ -z "$REPO_PATH" ]; then | |
| echo -n "Enter the repository path to scan (e.g., ~/repos): " | |
| read -r REPO_PATH | |
| fi | |
| # Expand tilde if present | |
| REPO_PATH="${REPO_PATH/#\~/$HOME}" | |
| if [ ! -d "$REPO_PATH" ]; then | |
| echo "Error: Directory '$REPO_PATH' does not exist." | |
| exit 1 | |
| fi | |
| if command -v rg >/dev/null 2>&1; then | |
| GREP_CMD="rg" | |
| else | |
| GREP_CMD="grep" | |
| fi | |
| FOUND_ANY=0 | |
| FOUND_GENERIC_PTH=0 | |
| print_header() { | |
| echo "========================================" | |
| echo " LiteLLM ${TARGET_VERSIONS_LABEL} Compromise Check Script" | |
| echo "========================================" | |
| echo "Scanning path: $REPO_PATH" | |
| echo "Date: $(date '+%Y-%m-%d %H:%M:%S %Z')" | |
| echo "" | |
| } | |
| print_step() { | |
| local step="$1" | |
| local title="$2" | |
| echo "---- [${step}] ${title} ----" | |
| } | |
| print_none_found() { | |
| echo "No findings." | |
| } | |
| mark_found() { | |
| FOUND_ANY=1 | |
| } | |
| safe_find() { | |
| # shellcheck disable=SC2048,SC2086 | |
| find $* 2>/dev/null | |
| } | |
| collect_python_bins() { | |
| { | |
| command -v python 2>/dev/null || true | |
| command -v python3 2>/dev/null || true | |
| safe_find "$REPO_PATH" -type f \( -path "*/.venv/bin/python" -o -path "*/venv/bin/python" -o -path "*/.venv/bin/python3" -o -path "*/venv/bin/python3" \) | |
| safe_find "$HOME" -maxdepth 4 -type f \( -path "*/.venv/bin/python" -o -path "*/.virtualenvs/*/bin/python" -o -path "*/.pyenv/versions/*/bin/python" \) | |
| } | awk 'NF' | sort -u | |
| } | |
| collect_site_packages_dirs() { | |
| { | |
| safe_find "$REPO_PATH" -type d -name site-packages | |
| safe_find "$HOME" -maxdepth 6 -type d -name site-packages | |
| [ -d "/usr/local/lib" ] && safe_find "/usr/local/lib" -maxdepth 4 -type d -name site-packages | |
| [ -d "/opt/homebrew/lib" ] && safe_find "/opt/homebrew/lib" -maxdepth 4 -type d -name site-packages | |
| } | awk 'NF' | sort -u | |
| } | |
| print_header | |
| # ===== 1. Check installed versions ===== | |
| print_step 1 "Checking installed litellm versions" | |
| PY_BINS="$(collect_python_bins)" | |
| if [ -z "$PY_BINS" ]; then | |
| echo "No Python interpreters discovered to inspect." | |
| else | |
| echo "Inspecting litellm via discovered Python interpreters..." | |
| while IFS= read -r pybin; do | |
| [ -x "$pybin" ] || continue | |
| echo "- Checking: $pybin" | |
| out="$($pybin -m pip show litellm 2>/dev/null | awk -F': ' '/^Name:|^Version:|^Location:/{print $0}')" | |
| if [ -n "$out" ]; then | |
| echo "$out" | |
| if echo "$out" | grep -Eq "Version: (${TARGET_VERSIONS_REGEX})"; then | |
| mark_found | |
| fi | |
| else | |
| echo " litellm not installed in this interpreter" | |
| fi | |
| done <<< "$PY_BINS" | |
| fi | |
| echo "" | |
| echo "Scanning site-packages METADATA for versions: ${TARGET_VERSIONS_LABEL}..." | |
| SITE_PKGS="$(collect_site_packages_dirs)" | |
| if [ -z "$SITE_PKGS" ]; then | |
| echo "No site-packages directories discovered." | |
| else | |
| METADATA_HITS=0 | |
| while IFS= read -r sp; do | |
| [ -d "$sp" ] || continue | |
| hits="$(safe_find "$sp" -type f -path "*/litellm-*.dist-info/METADATA" | xargs -I{} sh -c 'grep -Eq "Version: ('"${TARGET_VERSIONS_REGEX}"')" "{}" && echo "{}"' 2>/dev/null)" | |
| if [ -n "$hits" ]; then | |
| echo "$hits" | |
| METADATA_HITS=1 | |
| mark_found | |
| fi | |
| done <<< "$SITE_PKGS" | |
| [ "$METADATA_HITS" -eq 0 ] && print_none_found | |
| fi | |
| echo "" | |
| # ===== 2. Check for malicious .pth file ===== | |
| print_step 2 "Searching for litellm_init.pth" | |
| PTH_HITS="$( | |
| { | |
| safe_find "$REPO_PATH" -type f -name "litellm_init.pth" | |
| safe_find "$HOME" -maxdepth 7 -type f -name "litellm_init.pth" | |
| [ -d "/usr/local/lib" ] && safe_find "/usr/local/lib" -maxdepth 6 -type f -name "litellm_init.pth" | |
| [ -d "/opt/homebrew/lib" ] && safe_find "/opt/homebrew/lib" -maxdepth 6 -type f -name "litellm_init.pth" | |
| } | sort -u | |
| )" | |
| if [ -n "$PTH_HITS" ]; then | |
| echo "$PTH_HITS" | |
| mark_found | |
| echo "" | |
| echo "Inspecting litellm_init.pth contents:" | |
| while IFS= read -r pth; do | |
| [ -f "$pth" ] || continue | |
| echo "----- $pth -----" | |
| sed -n '1,120p' "$pth" | |
| done <<< "$PTH_HITS" | |
| else | |
| print_none_found | |
| fi | |
| echo "" | |
| # ===== 3. Scan dependency files ===== | |
| print_step 3 "Scanning dependency files" | |
| echo "Pinned to compromised versions (${TARGET_VERSIONS_LABEL}):" | |
| if [ "$GREP_CMD" = "rg" ]; then | |
| PINNED="$($GREP_CMD -l --hidden --glob '*.txt' --glob '*.toml' --glob '*.cfg' --glob '*.ini' --glob '!**/.venv/**' --glob '!**/venv/**' --glob '!**/site-packages/**' 'litellm\s*==\s*1\.(82\.7|82\.8)' "$REPO_PATH" 2>/dev/null)" | |
| else | |
| PINNED="$(grep -r -l --include='*.txt' --include='*.toml' --include='*.cfg' --include='*.ini' -E 'litellm==1\.(82\.7|82\.8)' "$REPO_PATH" 2>/dev/null)" | |
| fi | |
| if [ -n "$PINNED" ]; then | |
| echo "$PINNED" | |
| mark_found | |
| else | |
| print_none_found | |
| fi | |
| echo "" | |
| echo "Any litellm usage (dependency manifests only):" | |
| if [ "$GREP_CMD" = "rg" ]; then | |
| ANY_USAGE="$($GREP_CMD -l --hidden --glob '*.txt' --glob '*.toml' --glob '*.cfg' --glob '*.ini' --glob '!**/.venv/**' --glob '!**/venv/**' --glob '!**/site-packages/**' 'litellm' "$REPO_PATH" 2>/dev/null)" | |
| else | |
| ANY_USAGE="$(grep -r -l --include='*.txt' --include='*.toml' --include='*.cfg' --include='*.ini' 'litellm' "$REPO_PATH" 2>/dev/null)" | |
| fi | |
| if [ -n "$ANY_USAGE" ]; then | |
| echo "$ANY_USAGE" | |
| else | |
| print_none_found | |
| fi | |
| echo "" | |
| # ===== 4. Scan .pth files for suspicious patterns ===== | |
| print_step 4 "Scanning .pth files for incident indicators" | |
| if [ "$GREP_CMD" = "rg" ]; then | |
| SUSP_PTH="$( | |
| safe_find "$REPO_PATH" -type f -name '*.pth' | |
| safe_find "$HOME" -maxdepth 7 -type f -name '*.pth' | |
| [ -d "/usr/local/lib" ] && safe_find "/usr/local/lib" -maxdepth 6 -type f -name '*.pth' | |
| [ -d "/opt/homebrew/lib" ] && safe_find "/opt/homebrew/lib" -maxdepth 6 -type f -name '*.pth' | |
| )" | |
| if [ -n "$SUSP_PTH" ]; then | |
| IOC_MATCHES="$(echo "$SUSP_PTH" | xargs -I{} rg --with-filename -n 'litellm_init|litellm\.cloud|models\.litellm\.cloud|tpcp\.tar\.gz|/tmp/tpcp' {} 2>/dev/null)" | |
| if [ -n "$IOC_MATCHES" ]; then | |
| echo "$IOC_MATCHES" | |
| mark_found | |
| else | |
| echo "No incident-specific indicators in .pth files." | |
| fi | |
| echo "" | |
| echo "Generic risky patterns in .pth files (manual review):" | |
| GENERIC_PTH_MATCHES="$(echo "$SUSP_PTH" | xargs -I{} rg --with-filename -n 'base64|subprocess|Popen|exec\(|requests\.|urllib|socket' {} 2>/dev/null)" | |
| if [ -n "$GENERIC_PTH_MATCHES" ]; then | |
| echo "$GENERIC_PTH_MATCHES" | |
| FOUND_GENERIC_PTH=1 | |
| else | |
| print_none_found | |
| fi | |
| else | |
| print_none_found | |
| fi | |
| else | |
| print_none_found | |
| fi | |
| echo "" | |
| # ===== 5. Check potential exfiltration ===== | |
| print_step 5 "Checking shell history and DNS indicators" | |
| HISTORY_HITS="$(grep -h 'litellm\.cloud\|models\.litellm\.cloud' "$HOME/.bash_history" "$HOME/.zsh_history" "$HOME/.sh_history" 2>/dev/null)" | |
| if [ -n "$HISTORY_HITS" ]; then | |
| echo "$HISTORY_HITS" | |
| mark_found | |
| else | |
| echo "No suspicious shell history hits for litellm.cloud." | |
| fi | |
| echo "" | |
| echo "Checking DNS resolution for models.litellm.cloud:" | |
| if command -v host >/dev/null 2>&1; then | |
| host models.litellm.cloud 2>/dev/null || echo "Domain not resolving" | |
| elif command -v nslookup >/dev/null 2>&1; then | |
| nslookup models.litellm.cloud 2>/dev/null || echo "Domain not resolving" | |
| else | |
| echo "Neither 'host' nor 'nslookup' is available." | |
| fi | |
| echo "" | |
| # ===== 6. Check temp artifacts ===== | |
| print_step 6 "Checking /tmp for exfiltration artifacts" | |
| TMP_HITS="$( | |
| { | |
| safe_find /tmp -type f -name 'tpcp.tar.gz' | |
| safe_find /tmp -type f -name 'tpcp*' | |
| } | sort -u | |
| )" | |
| if [ -n "$TMP_HITS" ]; then | |
| echo "$TMP_HITS" | |
| mark_found | |
| else | |
| print_none_found | |
| fi | |
| echo "" | |
| echo "========================================" | |
| if [ "$FOUND_ANY" -eq 1 ]; then | |
| echo "Potential indicators found." | |
| else | |
| echo "No indicators found by this script." | |
| fi | |
| if [ "$FOUND_GENERIC_PTH" -eq 1 ]; then | |
| echo "Generic .pth patterns were found (not specific to this incident); review step [4]." | |
| fi | |
| echo "" | |
| echo "If litellm==1.82.7 or litellm==1.82.8 or litellm_init.pth is found:" | |
| echo "1. Immediately uninstall: pip uninstall litellm -y" | |
| echo "2. Rotate ALL secrets (SSH, cloud creds, API keys, etc.)" | |
| echo "3. Treat the system as COMPROMISED" | |
| echo "4. Rebuild affected environments from clean sources" | |
| echo "========================================" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment