
Supply Chain Guard
- 8 installs
- 30.1k repo stars
- Updated August 4, 2026
- davila7/claude-code-templates
Detect and remediate software supply chain attacks in npm, PyPI, crates.io, GitHub Actions, and CI/CD by scanning for compromised packages and IOCs.
About
Scans npm, PyPI, crates.io, GitHub Actions, and CI/CD pipelines for known compromised packages, malicious versions, filesystem IOCs, and C2 indicators. A developer uses it to detect and remediate supply chain attacks.
- Scans multiple package ecosystems for compromised versions
- Detects filesystem IOCs, C2 indicators, and CI/CD misconfigurations
Supply Chain Guard by the numbers
- 8 all-time installs (skills.sh)
- Ranked #1,681 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/davila7/claude-code-templates --skill supply-chain-guardAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 30.1k |
| Last updated | August 4, 2026 |
| Repository | davila7/claude-code-templates ↗ |
What it does
Detect and remediate software supply chain attacks in npm, PyPI, crates.io, GitHub Actions, and CI/CD by scanning for compromised packages and IOCs.
Files
Supply Chain Guard
Automated detection and remediation of software supply chain attacks across npm, PyPI, crates.io, GitHub Actions, and CI/CD pipelines. Built from real-world attack intelligence gathered through March 31, 2026.
When to Use This Skill
Use this skill when:
- The user asks to audit a project's dependencies for security issues
- Before deploying code to production
- When investigating a potential supply chain compromise
- When the user mentions a recent supply chain attack and wants to check their projects
- As a regular security check in development workflows
- When setting up CI/CD pipelines and wanting to harden them
- When a new supply chain attack is reported and the user wants to verify exposure
Instructions
Step 1: Understand the Project
Identify what the user's project uses:
- Node.js/npm: Look for
package.json,package-lock.json,yarn.lock,pnpm-lock.yaml - Python/PyPI: Look for
requirements.txt,Pipfile,pyproject.toml,poetry.lock - Rust/crates.io: Look for
Cargo.toml,Cargo.lock - CI/CD: Look for
.github/workflows/,Dockerfile,docker-compose.yml
Step 2: Run the Appropriate Scanners
The skill includes three specialized scanners plus a unified runner. All scripts are in the scripts/ directory.
Full audit (recommended):
bash /path/to/supply-chain-guard/scripts/scan-all.sh /path/to/projectIndividual scanners:
# npm/Node.js projects
bash /path/to/supply-chain-guard/scripts/scan-npm.sh /path/to/project
# Python/PyPI projects
bash /path/to/supply-chain-guard/scripts/scan-python.sh /path/to/project
# CI/CD pipeline audit
bash /path/to/supply-chain-guard/scripts/scan-ci.sh /path/to/projectEach scanner checks for: 1. Known compromised packages — exact matches against the IOC database 2. Malicious versions — specific version numbers known to contain malware 3. Filesystem IOCs — persistence mechanisms left by attackers 4. Network IOCs — C2 domains and IPs in source code 5. CI/CD misconfigurations — unpinned actions, dangerous triggers, exposed secrets 6. Credential exposure — npm tokens, PyPI credentials, .env files
Step 3: Interpret Results
Scanners exit with the number of issues found (0 = clean). Issues are categorized:
- [CRITICAL] — Known malicious package or active IOC detected. Immediate action required.
- [WARNING] — Security concern that needs investigation. May not be an active compromise.
Step 4: Remediate
Based on findings, guide the user through remediation:
If a compromised package is found:
1. Remove or downgrade to a known safe version immediately 2. Clear package caches: npm cache clean --force / pip cache purge 3. Delete node_modules / .venv and reinstall from lockfile 4. Rotate ALL credentials that were accessible from the environment
If filesystem IOCs are found:
1. The system should be treated as fully compromised 2. Identify and remove persistence mechanisms (systemd services, .pth files, cron jobs) 3. Rotate every credential on the system 4. Audit cloud provider logs (AWS CloudTrail, GCP Audit Logs, Azure Activity Log) 5. Check for lateral movement in Kubernetes clusters 6. Consider reimaging the machine
If CI/CD issues are found:
1. Pin all GitHub Actions to full commit SHAs (not version tags) 2. Add --ignore-scripts to npm install/ci commands 3. Add --require-hashes to pip install commands 4. Remove or secure pull_request_target triggers 5. Apply least-privilege permissions to workflow tokens 6. Audit pipeline execution logs for the attack window periods
Step 5: Harden the Project
After remediation, recommend these preventive measures:
1. Lock everything: Exact version pins + lockfiles committed to repo 2. Hash-verify: Use npm ci (not npm install), pip install --require-hashes 3. Disable scripts: Use --ignore-scripts by default, enable only for trusted packages 4. Pin actions: All GitHub Actions pinned to full SHA, never tags 5. Scope tokens: CI/CD tokens should have minimal permissions 6. Monitor: Set up automated dependency scanning (but verify the scanner itself is not compromised — see Trivy incident) 7. Network controls: Block known C2 domains/IPs at firewall level 8. Audit regularly: Run this scanner before every deployment
Reference Files
references/ioc-database.md— Full IOC database with all compromised packages, malicious versions, C2 infrastructure, filesystem indicators, and attack timelines. Read this file for detailed intelligence on specific attacks.
Current Threat Landscape (as of 2026-03-31)
Active Campaign: TeamPCP (CRITICAL)
The most significant active threat. TeamPCP is executing a cascading credential-chain campaign:
- Compromised Trivy (security scanner) → stole CI/CD secrets from thousands of pipelines
- Used stolen npm tokens to deploy CanisterWorm across 141+ npm packages
- Used stolen PyPI tokens to backdoor LiteLLM (95M monthly downloads) and Telnyx
- Uses blockchain (ICP) for C2, making takedown impossible
- Deploys WAV steganography for payload delivery
- Targets Kubernetes for lateral movement
- Has a destructive variant that wipes Iranian systems
Active: axios npm Hijack (2026-03-31)
- axios@1.14.1 and axios@0.30.4 contain RAT dropper via fake
plain-crypto-jsdependency - 300M+ weekly downloads makes this extremely high-impact
- Cross-platform RAT for macOS, Windows, and Linux
- Compromised maintainer account (jasonsaayman)
Recent: Malicious Rust Crates (2026-02/03)
- 5 crates impersonating time utilities on crates.io
- Steal .env files, AWS credentials, SSH keys
- First significant supply chain attack targeting Rust ecosystem
Historical but Relevant: Shai-Hulud Worm
- Self-replicating npm worm that compromised ~1000 packages
- Targets npm tokens for self-propagation
- Destructive fallback: wipes home directory if exfiltration fails
Updating the IOC Database
When new supply chain attacks are reported:
1. Search for the latest advisories from Socket, Aikido, Endor Labs, Snyk, JFrog 2. Update references/ioc-database.md with new packages, versions, domains, IPs 3. Update the scanner scripts with new package entries in the MALICIOUS_* arrays 4. Update the ioc-db-date in the SKILL.md frontmatter
Supply Chain IOC Database
Last updated: 2026-03-31
Active Campaigns
TeamPCP Campaign (March 19-31, 2026) — CRITICAL
The most consequential CI/CD supply chain attack documented to date. A cascading credential-chain campaign.
Timeline:
- Mar 19: Trivy backdoored (CVE-2026-33634, CVSS 9.4) — 75 trivy-action tags + 7 setup-trivy tags poisoned
- Mar 20: CanisterWorm deployed across 141+ npm package artifacts (66+ unique packages)
- Mar 22: WAV steganography variant detected; Checkmarx brand impersonation
- Mar 23: KICS GitHub Action (35 tags) + ast-github-action compromised
- Mar 24: LiteLLM v1.82.7/v1.82.8 published to PyPI with credential stealer
- Mar 27: Telnyx v4.87.1/v4.87.2 published to PyPI with WAV steganography payload
- Mar 31: axios@1.14.1 and axios@0.30.4 published to npm with RAT dropper
Attribution: TeamPCP (high confidence for Trivy/LiteLLM/Telnyx/CanisterWorm chain)
---
Compromised Packages — npm
| Package | Malicious Versions | Safe Version | Attack Type | Date |
|---|---|---|---|---|
| axios | 1.14.1, 0.30.4 | 1.14.0, 0.30.3 | RAT dropper via fake dep plain-crypto-js | 2026-03-31 |
| @emilgroup/* | 28 packages (various) | Prior versions | CanisterWorm backdoor | 2026-03-20 |
| @opengov/* | 16+ packages | Prior versions | CanisterWorm backdoor | 2026-03-20 |
| @teale.io/eslint-config | 1.8.11, 1.8.12 | Prior versions | Self-propagating worm | 2026-03-20 |
| @airtm/uuid-base32 | Various | Prior versions | CanisterWorm | 2026-03-20 |
| @pypestream/floating-ui-dom | Various | Prior versions | CanisterWorm | 2026-03-20 |
| plain-crypto-js | 4.2.1 | N/A (remove entirely) | RAT dropper | 2026-03-31 |
| n8n community nodes | Various malicious | N/A | OAuth/API key exfil | 2026-01 |
Historical (still relevant):
| Package | Versions | Safe | Attack | Date |
|---|---|---|---|---|
| chalk | Compromised versions (Sep 2025) | Verify hashes | Credential theft | 2025-09-08 |
| debug | Compromised versions (Sep 2025) | Verify hashes | Code injection | 2025-09-08 |
| @ctrl/tinycolor | Various | Verify | Shai-Hulud worm | 2025-09-11 |
| @asyncapi/specs | Various | Verify | Shai-Hulud worm | 2025-09-11 |
Compromised Packages — PyPI
| Package | Malicious Versions | Safe Version | Attack Type | Date |
|---|---|---|---|---|
| litellm | 1.82.7, 1.82.8 | <=1.82.6 | 3-stage credential stealer + K8s lateral | 2026-03-24 |
| telnyx | 4.87.1, 4.87.2 | 4.87.0 | WAV steganography credential stealer | 2026-03-27 |
| spellcheckerpy | All versions | N/A (remove) | RAT via Basque dictionary file | 2026-01-20 |
| spellcheckpy | All versions (esp 1.2.0+) | N/A (remove) | RAT via Basque dictionary file | 2026-01-21 |
| sympy-dev | All versions | N/A (remove, use sympy) | XMRig cryptominer | 2026-01-17 |
Compromised Packages — crates.io (Rust)
| Package | Attack Type | Date |
|---|---|---|
| chrono_anchor | .env exfil + credential theft | 2026-02/03 |
| dnp3times | .env exfil | 2026-02/03 |
| time_calibrator | .env exfil | 2026-02/03 |
| time_calibrators | .env exfil | 2026-02/03 |
| time-sync | .env exfil | 2026-02/03 |
Compromised GitHub Actions
| Action | Attack | Safe Practice | Date |
|---|---|---|---|
| aquasecurity/trivy-action | Tag poisoning (75/76 tags) | Pin to verified SHA | 2026-03-19 |
| aquasecurity/setup-trivy | Tag poisoning (all 7 tags) | Pin to verified SHA | 2026-03-19 |
| Checkmarx/kics-github-action | Tag poisoning (all 35 tags) | Pin to verified SHA | 2026-03-23 |
| Checkmarx/ast-github-action | v2.3.28 compromised | Pin to verified SHA | 2026-03-23 |
| tj-actions/changed-files | All version tags modified | Pin to verified SHA | 2025-03-14 |
Compromised VS Code / IDE Extensions
| Extension | Malicious Version | Platform | Date |
|---|---|---|---|
| ast-results | 2.53.0 | OpenVSX | 2026-03-23 |
| cx-dev-assist | 1.7.0 | OpenVSX | 2026-03-23 |
Network IOCs — Block These
C2 Domains
scan.aquasecurtiy[.]org(typosquat of aquasecurity)checkmarx[.]zonemodels.litellm[.]cloudupdatenet[.]work(spellchecker RAT)sfrclak[.]com(axios RAT — port 8000)timeapis[.]io(Rust crate exfil)
C2 IPs
45.148.10.21283.142.209.1183.142.209.203(Telnyx C2)172.86.73.139(spellchecker RAT)63.250.56.54(sympy-dev miner)
Blockchain C2
- ICP Canister:
tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io(CanisterWorm — untakeable)
Filesystem IOCs
Linux/macOS
/tmp/.fonts-unix/(AdaptixC2 binaries)~/.local/share/pgmon/service.py(CanisterWorm persistence)~/.config/sysmon/sysmon.py(LiteLLM stealer persistence)/tmp/ld.py(axios Linux payload)/Library/Caches/com.apple.act.mond(axios macOS RAT)- Systemd units:
pgmon.service,sysmon.service .pthfiles:litellm_init.pth(executes on every Python startup)
Windows
C:\Windows\Tasks\(DLL sideloading)%PROGRAMDATA%\wt.exe(axios Windows — copies PowerShell)- Startup folder:
msbuild.exe(Telnyx persistence)
Credential Paths Targeted by TeamPCP Stealer
The TeamPCP stealer (used in LiteLLM/Telnyx/CanisterWorm) scans 50+ paths:
~/.npmrc,/etc/npmrc(npm tokens)~/.aws/credentials,~/.aws/config~/.config/gcloud/~/.azure/~/.kube/config~/.docker/config.json~/.ssh/*~/.gnupg/~/.env,.envfiles in project dirs~/.gitconfig,.git/config~/.vault-token~/.pypirc- Shell histories:
~/.bash_history,~/.zsh_history - Browser credential stores
- AWS Secrets Manager contents and SSM parameters (via API)
#!/usr/bin/env bash
# supply-chain-guard: Full project supply chain audit
# Runs all scanners: npm, Python/PyPI, CI/CD, and system IOCs
# Usage: bash scan-all.sh [project_dir]
set -eo pipefail
PROJECT_DIR="${1:-.}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TOTAL_ISSUES=0
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m'
echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ supply-chain-guard — Full Supply Chain Audit ║${NC}"
echo -e "${CYAN}║ Project: $(printf '%-41s' "$PROJECT_DIR")║${NC}"
echo -e "${CYAN}║ Date: $(date '+%Y-%m-%d %H:%M %Z') ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
run_scanner() {
local scanner="$1"
local label="$2"
echo ""
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${CYAN} Running: $label${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
bash "$SCRIPT_DIR/$scanner" "$PROJECT_DIR"
local exit_code=$?
TOTAL_ISSUES=$((TOTAL_ISSUES + exit_code))
}
run_scanner "scan-npm.sh" "npm / Node.js Scanner"
run_scanner "scan-python.sh" "Python / PyPI Scanner"
run_scanner "scan-ci.sh" "CI/CD Pipeline Scanner"
# ============================================================
# FINAL SUMMARY
# ============================================================
echo ""
echo ""
echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ SUPPLY CHAIN AUDIT SUMMARY ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
if [[ $TOTAL_ISSUES -eq 0 ]]; then
echo -e "${GREEN}✓ All scanners passed. No known supply chain issues detected.${NC}"
echo ""
echo " Ongoing best practices:"
echo " • Keep lockfiles committed and up to date"
echo " • Pin all dependencies to exact versions"
echo " • Pin GitHub Actions to full commit SHAs"
echo " • Use --ignore-scripts for npm install"
echo " • Use --require-hashes for pip install"
echo " • Run this audit regularly and before deployments"
echo " • Subscribe to security advisories for your dependencies"
else
echo -e "${RED}✗ Total issues found across all scanners: $TOTAL_ISSUES${NC}"
echo ""
echo " Immediate response checklist:"
echo " ┌─────────────────────────────────────────────────────┐"
echo " │ 1. Remove/downgrade all flagged packages │"
echo " │ 2. Rotate ALL credentials: │"
echo " │ - npm/PyPI tokens │"
echo " │ - Cloud provider keys (AWS, GCP, Azure) │"
echo " │ - SSH keys │"
echo " │ - Database passwords │"
echo " │ - API keys and JWT secrets │"
echo " │ 3. Check for persistence mechanisms: │"
echo " │ - systemd services (pgmon, sysmon) │"
echo " │ - .pth files in Python site-packages │"
echo " │ - Cron jobs │"
echo " │ - Windows Startup folder │"
echo " │ 4. Audit network logs for C2 connections │"
echo " │ 5. If filesystem IOCs found: full incident response │"
echo " │ 6. Review CI/CD pipeline execution history │"
echo " └─────────────────────────────────────────────────────┘"
fi
echo ""
exit $TOTAL_ISSUES
#!/usr/bin/env bash
# supply-chain-guard: CI/CD Pipeline Security Scanner
# Audits GitHub Actions, Docker configurations, and CI/CD security posture
# Usage: bash scan-ci.sh [project_dir]
# Compatible with bash 3.2+ (macOS default)
set -eo pipefail
PROJECT_DIR="${1:-.}"
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m'
FOUND_ISSUES=0
echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ supply-chain-guard — CI/CD Pipeline Security Scanner ║${NC}"
echo -e "${CYAN}║ IOC Database: 2026-03-31 ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
alert() {
echo -e "${RED}[CRITICAL]${NC} $1"
FOUND_ISSUES=$((FOUND_ISSUES + 1))
}
warn() {
echo -e "${YELLOW}[WARNING]${NC} $1"
FOUND_ISSUES=$((FOUND_ISSUES + 1))
}
info() {
echo -e "${GREEN}[OK]${NC} $1"
}
# ============================================================
# PHASE 1: GitHub Actions Security Audit
# ============================================================
echo -e "${CYAN}── Phase 1: GitHub Actions Audit ──${NC}"
WORKFLOW_DIR="$PROJECT_DIR/.github/workflows"
if [[ -d "$WORKFLOW_DIR" ]]; then
TOTAL_WORKFLOWS=0
UNPINNED_COUNT=0
PULL_REQUEST_TARGET=0
# Compromised actions: "action_path|description"
COMPROMISED_ACTIONS=(
"aquasecurity/trivy-action|Tag-poisoned by TeamPCP Mar 19, 2026"
"aquasecurity/setup-trivy|Tag-poisoned by TeamPCP Mar 19, 2026"
"Checkmarx/kics-github-action|Tag-poisoned by TeamPCP Mar 23, 2026"
"Checkmarx/ast-github-action|v2.3.28 compromised Mar 23, 2026"
"tj-actions/changed-files|All tags modified (CVE-2025-30066)"
"reviewdog/action-setup|Compromised, led to tj-actions attack"
)
for yml in "$WORKFLOW_DIR"/*.yml "$WORKFLOW_DIR"/*.yaml; do
[[ -f "$yml" ]] || continue
TOTAL_WORKFLOWS=$((TOTAL_WORKFLOWS + 1))
BASENAME=$(basename "$yml")
# --- Check for known compromised actions ---
for entry in "${COMPROMISED_ACTIONS[@]}"; do
action="${entry%%|*}"
desc="${entry#*|}"
if grep -q "$action" "$yml" 2>/dev/null; then
if grep "$action" "$yml" | grep -qE "@[a-f0-9]{40}"; then
warn "$BASENAME: Uses $action (SHA-pinned). Verify SHA is from after remediation. $desc"
else
alert "$BASENAME: Uses $action WITHOUT SHA pinning. $desc"
fi
fi
done
# --- Check for actions not pinned to SHA ---
UNPINNED_LINES=$(grep -nE "uses:.*@(v[0-9]|latest|main|master|develop)" "$yml" 2>/dev/null || true)
if [[ -n "$UNPINNED_LINES" ]]; then
UNPINNED_IN_FILE=$(echo "$UNPINNED_LINES" | wc -l | tr -d ' ')
UNPINNED_COUNT=$((UNPINNED_COUNT + UNPINNED_IN_FILE))
warn "$BASENAME: $UNPINNED_IN_FILE action(s) pinned to mutable tags instead of commit SHAs."
echo "$UNPINNED_LINES" | head -5 | while read -r line; do echo " $line"; done
fi
# --- Check for pull_request_target ---
if grep -q "pull_request_target" "$yml" 2>/dev/null; then
PULL_REQUEST_TARGET=$((PULL_REQUEST_TARGET + 1))
warn "$BASENAME: Uses pull_request_target trigger — high-risk attack vector. Ensure checkout is from base, not PR head."
if grep -A5 "pull_request_target" "$yml" | grep -q "ref.*head\|github.event.pull_request.head" 2>/dev/null; then
alert "$BASENAME: pull_request_target + checkout of PR head detected. This is the exact pattern TeamPCP exploited."
fi
fi
# --- Check for overly permissive permissions ---
if grep -qE "permissions:.*write-all|permissions:.*contents:.*write" "$yml" 2>/dev/null; then
warn "$BASENAME: Workflow has write permissions. Apply least-privilege: use read-only unless write is required."
fi
# --- Check for secrets passed to third-party actions ---
if grep -E 'with:.*\$\{\{.*secrets\.' "$yml" 2>/dev/null | grep -v "actions/checkout\|actions/setup-" &>/dev/null; then
warn "$BASENAME: Secrets passed directly to third-party actions. Consider using OIDC or scoped tokens."
fi
# --- Check for npm/pip install without --ignore-scripts ---
if grep -qE "npm (ci|install)[^|]*$" "$yml" 2>/dev/null; then
if ! grep -qE "npm (ci|install).*--ignore-scripts" "$yml" 2>/dev/null; then
warn "$BASENAME: npm install/ci without --ignore-scripts. Postinstall scripts are a primary attack vector."
fi
fi
done
echo ""
info "Scanned $TOTAL_WORKFLOWS workflow file(s)."
[[ $UNPINNED_COUNT -gt 0 ]] && echo -e " ${YELLOW}Total unpinned actions: $UNPINNED_COUNT${NC}"
[[ $PULL_REQUEST_TARGET -gt 0 ]] && echo -e " ${YELLOW}Workflows with pull_request_target: $PULL_REQUEST_TARGET${NC}"
else
echo " No .github/workflows directory found."
fi
# ============================================================
# PHASE 2: Docker Security Check
# ============================================================
echo ""
echo -e "${CYAN}── Phase 2: Docker Configuration Audit ──${NC}"
DOCKERFILES=()
while IFS= read -r -d '' f; do
DOCKERFILES+=("$f")
done < <(find "$PROJECT_DIR" -maxdepth 3 \( -name "Dockerfile" -o -name "Dockerfile.*" -o -name "*.dockerfile" \) -print0 2>/dev/null)
if [[ ${#DOCKERFILES[@]} -gt 0 ]]; then
for dockerfile in "${DOCKERFILES[@]}"; do
echo " Scanning: $dockerfile"
if grep -qE "aquasec/trivy.*:.*0\.69\.[45]" "$dockerfile" 2>/dev/null; then
alert "$dockerfile: References compromised Trivy Docker image v0.69.4/v0.69.5/v0.69.6."
fi
if grep -qE "^FROM .+:latest" "$dockerfile" 2>/dev/null; then
warn "$dockerfile: Uses :latest tag. Pin to specific digest for reproducibility."
fi
if grep -qE "npm (install|ci)" "$dockerfile" 2>/dev/null; then
if ! grep -qE "npm (install|ci).*--ignore-scripts" "$dockerfile" 2>/dev/null; then
warn "$dockerfile: npm install without --ignore-scripts inside Docker build."
fi
fi
if grep -qE "pip install" "$dockerfile" 2>/dev/null; then
if ! grep -qE "pip install.*--require-hashes" "$dockerfile" 2>/dev/null; then
warn "$dockerfile: pip install without --require-hashes. Consider hash-pinning dependencies."
fi
fi
done
else
echo " No Dockerfiles found."
fi
COMPOSE_FILES=()
while IFS= read -r -d '' f; do
COMPOSE_FILES+=("$f")
done < <(find "$PROJECT_DIR" -maxdepth 2 \( -name "docker-compose*.yml" -o -name "docker-compose*.yaml" -o -name "compose.yml" -o -name "compose.yaml" \) -print0 2>/dev/null)
for compose in "${COMPOSE_FILES[@]}"; do
if grep -qE "aquasec/trivy" "$compose" 2>/dev/null; then
warn "$compose: References Trivy Docker image. Verify version is not in compromised range (v0.69.4-v0.69.6)."
fi
done
# ============================================================
# PHASE 3: Dependency Pinning & Lockfile Integrity
# ============================================================
echo ""
echo -e "${CYAN}── Phase 3: Dependency Pinning Audit ──${NC}"
if [[ -f "$PROJECT_DIR/package.json" ]]; then
if [[ ! -f "$PROJECT_DIR/package-lock.json" && ! -f "$PROJECT_DIR/yarn.lock" && ! -f "$PROJECT_DIR/pnpm-lock.yaml" ]]; then
warn "package.json exists but no lockfile found. Lockfiles are essential for reproducible builds."
fi
RANGE_DEPS=$(grep -cE '"[\^~]' "$PROJECT_DIR/package.json" 2>/dev/null || echo "0")
if [[ "$RANGE_DEPS" -gt 0 ]]; then
warn "$RANGE_DEPS dependencies use range specifiers (^ or ~). Consider exact pinning for security-critical projects."
fi
fi
if [[ -f "$PROJECT_DIR/requirements.txt" ]]; then
UNPINNED_PY=$(grep -cvE "==|#|^$|^-" "$PROJECT_DIR/requirements.txt" 2>/dev/null || echo "0")
if [[ "$UNPINNED_PY" -gt 0 ]]; then
warn "requirements.txt has $UNPINNED_PY dependencies without exact version pins (==)."
fi
fi
# ============================================================
# PHASE 4: Secret Exposure Risk
# ============================================================
echo ""
echo -e "${CYAN}── Phase 4: Secret Exposure Risk Assessment ──${NC}"
if command -v git &>/dev/null && [[ -d "$PROJECT_DIR/.git" ]]; then
ENV_IN_GIT=$(git -C "$PROJECT_DIR" ls-files | grep -E "\.env$|\.env\." 2>/dev/null || true)
if [[ -n "$ENV_IN_GIT" ]]; then
# Filter out .env.example files (these are safe)
REAL_ENV=$(echo "$ENV_IN_GIT" | grep -v "\.env\.example" || true)
if [[ -n "$REAL_ENV" ]]; then
alert ".env file(s) tracked in git — credentials may be exposed in history:"
echo "$REAL_ENV" | while read -r f; do echo " - $f"; done
else
info ".env.example files found (safe — no real secrets)."
fi
fi
if [[ -f "$PROJECT_DIR/.gitignore" ]]; then
if ! grep -q "\.env" "$PROJECT_DIR/.gitignore" 2>/dev/null; then
warn ".gitignore does not include .env pattern."
fi
else
warn "No .gitignore found. Ensure sensitive files are not committed."
fi
fi
# ============================================================
# RESULTS
# ============================================================
echo ""
echo -e "${CYAN}══════════════════════════════════════════════════════════${NC}"
if [[ $FOUND_ISSUES -eq 0 ]]; then
echo -e "${GREEN}✓ CI/CD pipeline audit passed with no issues.${NC}"
else
echo -e "${RED}✗ Found $FOUND_ISSUES potential issue(s) in CI/CD configuration.${NC}"
echo ""
echo " Key recommendations:"
echo " 1. Pin ALL GitHub Actions to full commit SHAs"
echo " 2. Avoid pull_request_target unless absolutely necessary"
echo " 3. Use --ignore-scripts for npm install in CI"
echo " 4. Pin Docker base images to digests"
echo " 5. Use --require-hashes for pip install in CI"
echo " 6. Apply least-privilege permissions to workflow tokens"
fi
echo -e "${CYAN}══════════════════════════════════════════════════════════${NC}"
exit $FOUND_ISSUES
#!/usr/bin/env bash
# supply-chain-guard: npm supply chain scanner
# Scans node_modules and lockfiles for known compromised packages and IOCs
# Usage: bash scan-npm.sh [project_dir]
# Compatible with bash 3.2+ (macOS default)
set -eo pipefail
PROJECT_DIR="${1:-.}"
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m'
FOUND_ISSUES=0
echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ supply-chain-guard — npm Supply Chain Scanner ║${NC}"
echo -e "${CYAN}║ IOC Database: 2026-03-31 ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
alert() {
echo -e "${RED}[CRITICAL]${NC} $1"
FOUND_ISSUES=$((FOUND_ISSUES + 1))
}
warn() {
echo -e "${YELLOW}[WARNING]${NC} $1"
FOUND_ISSUES=$((FOUND_ISSUES + 1))
}
info() {
echo -e "${GREEN}[OK]${NC} $1"
}
# --- Known malicious packages: "name|description" ---
MALICIOUS_EXACT=(
"plain-crypto-js|RAT dropper (axios campaign, 2026-03-31). Remove immediately."
"spellcheckerpy|RAT trojan. Remove immediately."
"spellcheckpy|RAT trojan. Remove immediately."
)
# --- Packages with known malicious versions: "name|bad_versions|info" ---
MALICIOUS_VERSIONED=(
"axios|1.14.1,0.30.4|RAT dropper via plain-crypto-js dependency (2026-03-31). Downgrade to 1.14.0 or 0.30.3."
"litellm|1.82.7,1.82.8|TeamPCP credential stealer (2026-03-24). Downgrade to <=1.82.6."
"telnyx|4.87.1,4.87.2|TeamPCP WAV steganography stealer (2026-03-27). Downgrade to 4.87.0."
)
# --- Compromised npm scopes ---
MALICIOUS_SCOPES=("@emilgroup" "@opengov" "@teale.io" "@airtm" "@pypestream")
# --- Filesystem IOCs ---
FS_IOCS=(
"$HOME/.local/share/pgmon/service.py"
"$HOME/.config/sysmon/sysmon.py"
"/tmp/.fonts-unix"
"/tmp/ld.py"
)
if [[ "$OSTYPE" == "darwin"* ]]; then
FS_IOCS+=("/Library/Caches/com.apple.act.mond")
fi
# --- Network IOCs ---
MALICIOUS_DOMAINS=(
"scan.aquasecurtiy.org"
"checkmarx.zone"
"models.litellm.cloud"
"updatenet.work"
"sfrclak.com"
"timeapis.io"
"tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io"
)
MALICIOUS_IPS=(
"45.148.10.212"
"83.142.209.11"
"83.142.209.203"
"172.86.73.139"
"63.250.56.54"
)
# ============================================================
# PHASE 1: Check lockfiles for compromised packages/versions
# ============================================================
echo -e "${CYAN}── Phase 1: Scanning lockfiles ──${NC}"
LOCKFILES=()
for f in "$PROJECT_DIR/package-lock.json" "$PROJECT_DIR/yarn.lock" "$PROJECT_DIR/pnpm-lock.yaml" "$PROJECT_DIR/bun.lockb"; do
[[ -f "$f" ]] && LOCKFILES+=("$f")
done
if [[ ${#LOCKFILES[@]} -eq 0 ]]; then
echo " No lockfile found in $PROJECT_DIR. Checking package.json only."
fi
# Check package.json
if [[ -f "$PROJECT_DIR/package.json" ]]; then
for entry in "${MALICIOUS_EXACT[@]}"; do
pkg="${entry%%|*}"
desc="${entry#*|}"
if grep -q "\"$pkg\"" "$PROJECT_DIR/package.json" 2>/dev/null; then
alert "package.json contains known malicious package: $pkg — $desc"
fi
done
for entry in "${MALICIOUS_VERSIONED[@]}"; do
pkg="${entry%%|*}"
rest="${entry#*|}"
versions="${rest%%|*}"
desc="${rest#*|}"
if grep -q "\"$pkg\"" "$PROJECT_DIR/package.json" 2>/dev/null; then
IFS=',' read -ra BAD_VERS <<< "$versions"
for ver in "${BAD_VERS[@]}"; do
if grep -qE "\"$pkg\"[[:space:]]*:[[:space:]]*\"[~^]?$ver\"" "$PROJECT_DIR/package.json" 2>/dev/null; then
alert "$pkg@$ver in package.json — $desc"
fi
done
fi
done
fi
# Check lockfiles
for lockfile in "${LOCKFILES[@]}"; do
echo " Scanning: $lockfile"
for entry in "${MALICIOUS_EXACT[@]}"; do
pkg="${entry%%|*}"
desc="${entry#*|}"
if grep -q "$pkg" "$lockfile" 2>/dev/null; then
alert "Lockfile contains known malicious package: $pkg — $desc"
fi
done
for entry in "${MALICIOUS_VERSIONED[@]}"; do
pkg="${entry%%|*}"
rest="${entry#*|}"
versions="${rest%%|*}"
desc="${rest#*|}"
IFS=',' read -ra BAD_VERS <<< "$versions"
for ver in "${BAD_VERS[@]}"; do
if grep -q "${pkg}.*${ver}" "$lockfile" 2>/dev/null || grep -q "\"${pkg}\": \"${ver}\"" "$lockfile" 2>/dev/null; then
alert "$pkg@$ver found in lockfile — $desc"
fi
done
done
for scope in "${MALICIOUS_SCOPES[@]}"; do
if grep -q "$scope/" "$lockfile" 2>/dev/null; then
warn "Lockfile references packages from compromised scope $scope (CanisterWorm). Verify versions."
fi
done
done
# ============================================================
# PHASE 2: Check node_modules for malicious packages
# ============================================================
echo ""
echo -e "${CYAN}── Phase 2: Scanning node_modules ──${NC}"
if [[ -d "$PROJECT_DIR/node_modules" ]]; then
for entry in "${MALICIOUS_EXACT[@]}"; do
pkg="${entry%%|*}"
desc="${entry#*|}"
if [[ -d "$PROJECT_DIR/node_modules/$pkg" ]]; then
alert "node_modules contains malicious package: $pkg — $desc"
fi
done
for scope in "${MALICIOUS_SCOPES[@]}"; do
if [[ -d "$PROJECT_DIR/node_modules/$scope" ]]; then
warn "node_modules contains packages from compromised scope $scope. Audit all packages in this scope."
fi
done
echo " Checking for suspicious postinstall scripts..."
SUSPECT_SCRIPTS=$(find "$PROJECT_DIR/node_modules" -maxdepth 3 -name "package.json" -exec grep -l '"postinstall"' {} \; 2>/dev/null | head -20 || true)
if [[ -n "$SUSPECT_SCRIPTS" ]]; then
SCRIPT_COUNT=$(echo "$SUSPECT_SCRIPTS" | wc -l | tr -d ' ')
echo -e " ${YELLOW}Found $SCRIPT_COUNT packages with postinstall scripts. Review manually:${NC}"
echo "$SUSPECT_SCRIPTS" | head -10 | while read -r f; do
echo " - $f"
done
[[ "$SCRIPT_COUNT" -gt 10 ]] && echo " ... and $((SCRIPT_COUNT - 10)) more"
fi
else
echo " No node_modules found (not installed or using a different package manager)."
fi
# ============================================================
# PHASE 3: Check GitHub Actions for compromised actions
# ============================================================
echo ""
echo -e "${CYAN}── Phase 3: Scanning GitHub Actions workflows ──${NC}"
WORKFLOW_DIR="$PROJECT_DIR/.github/workflows"
if [[ -d "$WORKFLOW_DIR" ]]; then
COMPROMISED_ACTIONS=(
"aquasecurity/trivy-action|75/76 tags poisoned by TeamPCP (2026-03-19). Pin to verified commit SHA."
"aquasecurity/setup-trivy|All 7 tags poisoned (2026-03-19). Pin to verified commit SHA."
"Checkmarx/kics-github-action|All 35 tags poisoned (2026-03-23). Pin to verified commit SHA."
"Checkmarx/ast-github-action|v2.3.28 compromised (2026-03-23). Pin to verified commit SHA."
"tj-actions/changed-files|All version tags modified (CVE-2025-30066). Pin to verified commit SHA."
)
for yml in "$WORKFLOW_DIR"/*.yml "$WORKFLOW_DIR"/*.yaml; do
[[ -f "$yml" ]] || continue
for entry in "${COMPROMISED_ACTIONS[@]}"; do
action="${entry%%|*}"
desc="${entry#*|}"
if grep -q "$action" "$yml" 2>/dev/null; then
if grep "$action" "$yml" | grep -qE "@[a-f0-9]{40}"; then
warn "$action used in $(basename "$yml") — SHA-pinned (good), but verify the SHA is not from the attack window. $desc"
else
alert "$action used in $(basename "$yml") WITHOUT SHA pinning — $desc"
fi
fi
done
UNPINNED=$(grep -E "uses:.*@(v[0-9]|latest|main|master)" "$yml" 2>/dev/null || true)
if [[ -n "$UNPINNED" ]]; then
warn "$(basename "$yml") has actions pinned to mutable tags (v*, latest, main). Pin all actions to commit SHAs."
fi
done
else
echo " No .github/workflows directory found."
fi
# ============================================================
# PHASE 4: Filesystem IOC scan
# ============================================================
echo ""
echo -e "${CYAN}── Phase 4: Filesystem IOC scan ──${NC}"
for ioc in "${FS_IOCS[@]}"; do
if [[ -e "$ioc" ]]; then
alert "Filesystem IOC detected: $ioc — System may be compromised. Investigate immediately."
fi
done
if command -v systemctl &>/dev/null; then
for svc in "pgmon.service" "sysmon.service"; do
if systemctl --user is-enabled "$svc" &>/dev/null 2>&1 || systemctl is-enabled "$svc" &>/dev/null 2>&1; then
alert "Suspicious systemd service detected: $svc — Known TeamPCP persistence mechanism."
fi
done
fi
if command -v python3 &>/dev/null; then
SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])" 2>/dev/null || echo "")
if [[ -n "$SITE_PACKAGES" && -d "$SITE_PACKAGES" ]]; then
SUSPICIOUS_PTH=$(find "$SITE_PACKAGES" -name "*.pth" -newer "$SITE_PACKAGES" -exec grep -l "exec\|import\|eval\|subprocess\|os.system" {} \; 2>/dev/null || true)
if [[ -n "$SUSPICIOUS_PTH" ]]; then
alert "Suspicious .pth files with code execution found in site-packages:"
echo "$SUSPICIOUS_PTH" | while read -r f; do echo " - $f"; done
fi
fi
fi
if [[ -d "/mnt/c/Users" ]]; then
for user_dir in /mnt/c/Users/*/AppData/Roaming/Microsoft/Windows/Start\ Menu/Programs/Startup/; do
if [[ -f "${user_dir}msbuild.exe" ]]; then
alert "Telnyx persistence IOC: msbuild.exe found in Windows Startup folder: $user_dir"
fi
done
fi
# ============================================================
# PHASE 5: Network IOC scan in source files
# ============================================================
echo ""
echo -e "${CYAN}── Phase 5: Network IOC scan ──${NC}"
SCAN_DIRS=("$PROJECT_DIR/node_modules" "$PROJECT_DIR/src" "$PROJECT_DIR/lib" "$PROJECT_DIR/dist")
for domain in "${MALICIOUS_DOMAINS[@]}"; do
for dir in "${SCAN_DIRS[@]}"; do
[[ -d "$dir" ]] || continue
HITS=$(grep -rl "$domain" "$dir" 2>/dev/null | head -5 || true)
if [[ -n "$HITS" ]]; then
alert "Malicious C2 domain '$domain' found in files:"
echo "$HITS" | while read -r f; do echo " - $f"; done
fi
done
done
for ip in "${MALICIOUS_IPS[@]}"; do
for dir in "${SCAN_DIRS[@]}"; do
[[ -d "$dir" ]] || continue
HITS=$(grep -rl "$ip" "$dir" 2>/dev/null | head -5 || true)
if [[ -n "$HITS" ]]; then
alert "Malicious C2 IP '$ip' found in files:"
echo "$HITS" | while read -r f; do echo " - $f"; done
fi
done
done
# ============================================================
# PHASE 6: Check .npmrc for token exposure
# ============================================================
echo ""
echo -e "${CYAN}── Phase 6: npm token exposure check ──${NC}"
for npmrc in "$HOME/.npmrc" "$PROJECT_DIR/.npmrc" "/etc/npmrc"; do
if [[ -f "$npmrc" ]]; then
if grep -q "_authToken" "$npmrc" 2>/dev/null; then
warn "npm auth token found in $npmrc — Ensure this token has minimal scopes and rotate if any compromise suspected."
fi
fi
done
# ============================================================
# RESULTS
# ============================================================
echo ""
echo -e "${CYAN}══════════════════════════════════════════════════════════${NC}"
if [[ $FOUND_ISSUES -eq 0 ]]; then
echo -e "${GREEN}✓ No known supply chain IOCs detected.${NC}"
echo " Note: This scanner checks against known IOCs as of 2026-03-31."
echo " Always keep dependencies pinned and use lockfiles."
else
echo -e "${RED}✗ Found $FOUND_ISSUES potential issue(s).${NC}"
echo ""
echo " Recommended immediate actions:"
echo " 1. Remove/downgrade any flagged packages"
echo " 2. Rotate ALL credentials accessible from this environment"
echo " 3. Audit CI/CD pipeline secrets"
echo " 4. Check network logs for connections to flagged C2 infrastructure"
echo " 5. If filesystem IOCs found: treat system as fully compromised"
fi
echo -e "${CYAN}══════════════════════════════════════════════════════════${NC}"
exit $FOUND_ISSUES
#!/usr/bin/env bash
# supply-chain-guard: Python/PyPI supply chain scanner
# Scans Python environments, requirements files, and Pipfiles for known compromised packages
# Usage: bash scan-python.sh [project_dir]
# Compatible with bash 3.2+ (macOS default)
set -eo pipefail
PROJECT_DIR="${1:-.}"
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m'
FOUND_ISSUES=0
echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ supply-chain-guard — Python/PyPI Supply Chain Scanner ║${NC}"
echo -e "${CYAN}║ IOC Database: 2026-03-31 ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
alert() {
echo -e "${RED}[CRITICAL]${NC} $1"
FOUND_ISSUES=$((FOUND_ISSUES + 1))
}
warn() {
echo -e "${YELLOW}[WARNING]${NC} $1"
FOUND_ISSUES=$((FOUND_ISSUES + 1))
}
info() {
echo -e "${GREEN}[OK]${NC} $1"
}
# --- Known malicious packages (remove entirely) ---
MALICIOUS_REMOVE=("spellcheckerpy" "spellcheckpy" "sympy-dev")
# --- Packages with known malicious versions: "name|bad_versions|info" ---
MALICIOUS_VERSIONED=(
"litellm|1.82.7,1.82.8|TeamPCP 3-stage credential stealer + K8s lateral movement (2026-03-24). Downgrade to <=1.82.6."
"telnyx|4.87.1,4.87.2|TeamPCP WAV steganography payload (2026-03-27). Downgrade to 4.87.0."
)
# --- Filesystem IOCs ---
FS_IOCS=(
"$HOME/.local/share/pgmon/service.py"
"$HOME/.config/sysmon/sysmon.py"
"/tmp/.fonts-unix"
"/tmp/ld.py"
)
# ============================================================
# PHASE 1: Scan requirements files
# ============================================================
echo -e "${CYAN}── Phase 1: Scanning requirements/dependency files ──${NC}"
REQ_FILES=()
while IFS= read -r -d '' f; do
REQ_FILES+=("$f")
done < <(find "$PROJECT_DIR" -maxdepth 3 \( -name "requirements*.txt" -o -name "Pipfile" -o -name "Pipfile.lock" -o -name "pyproject.toml" -o -name "setup.py" -o -name "setup.cfg" -o -name "poetry.lock" \) -not -path "*/node_modules/*" -not -path "*/.venv/*" -not -path "*/venv/*" -print0 2>/dev/null)
if [[ ${#REQ_FILES[@]} -eq 0 ]]; then
echo " No Python dependency files found in $PROJECT_DIR."
else
for reqfile in "${REQ_FILES[@]}"; do
echo " Scanning: $reqfile"
for pkg in "${MALICIOUS_REMOVE[@]}"; do
if grep -qi "^${pkg}\b\|^${pkg}[=><!~]" "$reqfile" 2>/dev/null || grep -qi "\"${pkg}\"" "$reqfile" 2>/dev/null; then
alert "$pkg found in $reqfile — Known malicious package. Remove immediately."
fi
done
for entry in "${MALICIOUS_VERSIONED[@]}"; do
pkg="${entry%%|*}"
rest="${entry#*|}"
versions="${rest%%|*}"
desc="${rest#*|}"
if grep -qi "$pkg" "$reqfile" 2>/dev/null; then
IFS=',' read -ra BAD_VERS <<< "$versions"
for ver in "${BAD_VERS[@]}"; do
if grep -qi "${pkg}[=~><!]*=${ver}\|${pkg}==${ver}" "$reqfile" 2>/dev/null; then
alert "$pkg==$ver in $reqfile — $desc"
fi
done
if grep -qiE "^${pkg}$|^${pkg}[^=]" "$reqfile" 2>/dev/null; then
warn "$pkg in $reqfile is not pinned to a specific version. Pin to a known safe version."
fi
fi
done
done
fi
# ============================================================
# PHASE 2: Scan installed Python packages
# ============================================================
echo ""
echo -e "${CYAN}── Phase 2: Scanning installed Python packages ──${NC}"
if command -v pip &>/dev/null || command -v pip3 &>/dev/null; then
PIP_CMD=$(command -v pip3 || command -v pip)
INSTALLED=$($PIP_CMD list --format=freeze 2>/dev/null || true)
if [[ -n "$INSTALLED" ]]; then
for pkg in "${MALICIOUS_REMOVE[@]}"; do
if echo "$INSTALLED" | grep -qi "^${pkg}=="; then
alert "Installed malicious package detected: $pkg — Uninstall with: pip uninstall $pkg"
fi
done
for entry in "${MALICIOUS_VERSIONED[@]}"; do
pkg="${entry%%|*}"
rest="${entry#*|}"
versions="${rest%%|*}"
desc="${rest#*|}"
IFS=',' read -ra BAD_VERS <<< "$versions"
for ver in "${BAD_VERS[@]}"; do
if echo "$INSTALLED" | grep -qi "^${pkg}==${ver}"; then
alert "Compromised version installed: $pkg==$ver — $desc"
fi
done
done
info "Scanned $(echo "$INSTALLED" | wc -l | tr -d ' ') installed packages."
fi
else
echo " pip not found. Skipping installed package scan."
fi
# Also check virtualenv if present
for venv_dir in "$PROJECT_DIR/.venv" "$PROJECT_DIR/venv" "$PROJECT_DIR/env"; do
if [[ -f "$venv_dir/bin/pip" ]]; then
echo " Scanning virtualenv: $venv_dir"
VENV_INSTALLED=$("$venv_dir/bin/pip" list --format=freeze 2>/dev/null || true)
for pkg in "${MALICIOUS_REMOVE[@]}"; do
if echo "$VENV_INSTALLED" | grep -qi "^${pkg}=="; then
alert "Malicious package in virtualenv $venv_dir: $pkg"
fi
done
for entry in "${MALICIOUS_VERSIONED[@]}"; do
pkg="${entry%%|*}"
rest="${entry#*|}"
versions="${rest%%|*}"
desc="${rest#*|}"
IFS=',' read -ra BAD_VERS <<< "$versions"
for ver in "${BAD_VERS[@]}"; do
if echo "$VENV_INSTALLED" | grep -qi "^${pkg}==${ver}"; then
alert "Compromised version in $venv_dir: $pkg==$ver — $desc"
fi
done
done
fi
done
# ============================================================
# PHASE 3: Check for .pth file hijacking
# ============================================================
echo ""
echo -e "${CYAN}── Phase 3: Checking for .pth file hijacking ──${NC}"
if command -v python3 &>/dev/null; then
SITE_DIRS=$(python3 -c "import site; [print(p) for p in site.getsitepackages()]" 2>/dev/null || true)
USER_SITE=$(python3 -c "import site; print(site.getusersitepackages())" 2>/dev/null || true)
ALL_SITES="$SITE_DIRS"
[[ -n "$USER_SITE" ]] && ALL_SITES="$ALL_SITES"$'\n'"$USER_SITE"
while IFS= read -r site_dir; do
[[ -d "$site_dir" ]] || continue
while IFS= read -r -d '' pth_file; do
if grep -qE "^import |^exec|^eval|subprocess|os\.system|os\.popen|__import__" "$pth_file" 2>/dev/null; then
alert "Suspicious .pth file with code execution: $pth_file"
echo " Content preview:"
head -5 "$pth_file" | while read -r line; do echo " $line"; done
fi
if [[ "$(basename "$pth_file")" == "litellm_init.pth" ]]; then
alert "LiteLLM persistence IOC: litellm_init.pth found at $pth_file — This executes on EVERY Python startup."
fi
done < <(find "$site_dir" -name "*.pth" -print0 2>/dev/null)
done <<< "$ALL_SITES"
else
echo " python3 not found. Skipping .pth scan."
fi
# ============================================================
# PHASE 4: Check for PyPI token exposure
# ============================================================
echo ""
echo -e "${CYAN}── Phase 4: PyPI credential exposure check ──${NC}"
PYPIRC="$HOME/.pypirc"
if [[ -f "$PYPIRC" ]]; then
warn ".pypirc found at $PYPIRC — Contains PyPI publishing credentials. Ensure token has minimal scope."
if grep -q "password" "$PYPIRC" 2>/dev/null; then
warn ".pypirc contains a password field. Use scoped API tokens instead."
fi
fi
if env | grep -qi "PYPI\|TWINE" 2>/dev/null; then
warn "PyPI/Twine credentials found in environment variables. Rotate if any compromise suspected."
fi
# ============================================================
# PHASE 5: Filesystem IOC scan
# ============================================================
echo ""
echo -e "${CYAN}── Phase 5: Filesystem IOC scan ──${NC}"
for ioc in "${FS_IOCS[@]}"; do
if [[ -e "$ioc" ]]; then
alert "Filesystem IOC detected: $ioc — System may be compromised."
fi
done
if command -v systemctl &>/dev/null; then
for svc in "pgmon.service" "sysmon.service"; do
if systemctl --user is-enabled "$svc" &>/dev/null 2>&1; then
alert "TeamPCP persistence mechanism: systemd user service $svc is enabled."
fi
done
fi
if command -v crontab &>/dev/null; then
CRON=$(crontab -l 2>/dev/null || true)
if echo "$CRON" | grep -qE "pgmon|sysmon|\.fonts-unix|icp0\.io" 2>/dev/null; then
alert "Suspicious cron entry found matching known IOC patterns."
fi
fi
# ============================================================
# PHASE 6: Check Rust crates (if Cargo.lock present)
# ============================================================
echo ""
echo -e "${CYAN}── Phase 6: Checking Rust dependencies (if present) ──${NC}"
CARGO_LOCKS=()
while IFS= read -r -d '' f; do
CARGO_LOCKS+=("$f")
done < <(find "$PROJECT_DIR" -maxdepth 3 -name "Cargo.lock" -not -path "*/target/*" -print0 2>/dev/null)
if [[ ${#CARGO_LOCKS[@]} -gt 0 ]]; then
MALICIOUS_CRATES=("chrono_anchor" "dnp3times" "time_calibrator" "time_calibrators" "time-sync")
for cargo_lock in "${CARGO_LOCKS[@]}"; do
echo " Scanning: $cargo_lock"
for crate in "${MALICIOUS_CRATES[@]}"; do
if grep -q "name = \"$crate\"" "$cargo_lock" 2>/dev/null; then
alert "Malicious Rust crate in $cargo_lock: $crate — Credential stealer targeting .env files. Remove immediately."
fi
done
done
else
echo " No Cargo.lock found."
fi
# ============================================================
# RESULTS
# ============================================================
echo ""
echo -e "${CYAN}══════════════════════════════════════════════════════════${NC}"
if [[ $FOUND_ISSUES -eq 0 ]]; then
echo -e "${GREEN}✓ No known supply chain IOCs detected.${NC}"
echo " Note: This scanner checks against known IOCs as of 2026-03-31."
echo " Always pin dependencies and verify package integrity."
else
echo -e "${RED}✗ Found $FOUND_ISSUES potential issue(s).${NC}"
echo ""
echo " Recommended immediate actions:"
echo " 1. Uninstall/downgrade any flagged packages"
echo " 2. Rotate ALL credentials: PyPI tokens, cloud keys, SSH keys, API keys"
echo " 3. Check for persistence: systemd services, .pth files, cron jobs"
echo " 4. Audit CI/CD pipelines that run pip install"
echo " 5. If filesystem IOCs found: treat system as fully compromised"
fi
echo -e "${CYAN}══════════════════════════════════════════════════════════${NC}"
exit $FOUND_ISSUES