$npx -y skills add xwtro0tk1t-cloud/harness --skill supply-chain-audit多语言供应链投毒检测。支持 python(.pth投毒/setup.py hooks)、npm(postinstall hooks/eval混淆)、go(init() 后门/go:generate)、rust(build.rs)、ruby(extconf.rb)、java(Maven plugin/Gradle exec)、php(composer scripts)。当用户担心供应链安全、想检查依赖投毒、或提到 litellm/ua-parser-js/event-stream 等供应链攻击事件时使用。
| 1 | # Supply Chain Audit — 多语言供应链投毒检测 |
| 2 | |
| 3 | 你正在执行供应链投毒审计。不同语言的包管理器有各自的自动执行入口,攻击者通过这些入口在安装、构建或启动时自动执行恶意代码。 |
| 4 | |
| 5 | ## 参数解析 |
| 6 | |
| 7 | 用户输入: `$ARGUMENTS` |
| 8 | |
| 9 | **解析规则**: |
| 10 | - 如果 `$ARGUMENTS` 为空或仅含路径,**询问用户想审计哪些生态系统**,列出支持的选项:`python`、`npm`、`go`、`rust`、`ruby`、`java`、`php`、`all` |
| 11 | - 如果包含生态系统名称,提取所有匹配项并依次执行 |
| 12 | - `all` = 检测当前环境中存在的所有生态系统 |
| 13 | - 可以组合:`/supply-chain-audit npm python` 同时审计两个 |
| 14 | |
| 15 | --- |
| 16 | |
| 17 | ## Python 审计模块 |
| 18 | |
| 19 | **检测目标**:`.pth` 文件投毒 + `setup.py`/`pyproject.toml` 恶意 hooks |
| 20 | |
| 21 | ### Python Step 1: .pth 文件扫描 |
| 22 | |
| 23 | ```bash |
| 24 | python3 << 'PY_PTH_AUDIT' |
| 25 | import os, sys, site, re, json, hashlib |
| 26 | from datetime import datetime, timezone |
| 27 | |
| 28 | CRITICAL_PATTERNS = [ |
| 29 | (r'import\s+.*exec\s*\(', 'import + exec(): 动态代码执行'), |
| 30 | (r'import\s+.*eval\s*\(', 'import + eval(): 动态表达式求值'), |
| 31 | (r'import\s+.*\bbase64\b.*decode', 'import + base64 decode: 编码混淆载荷'), |
| 32 | (r'import\s+.*\bcompile\s*\(', 'import + compile(): 动态编译代码'), |
| 33 | (r'import\s+.*__import__\s*\(', 'import + __import__(): 动态模块加载'), |
| 34 | (r'import\s+.*\bsubprocess\b', 'import + subprocess: 执行系统命令'), |
| 35 | (r'import\s+.*\burllib\b', 'import + urllib: 网络请求'), |
| 36 | (r'import\s+.*\brequests\b', 'import + requests: 网络请求'), |
| 37 | (r'import\s+.*\bsocket\b', 'import + socket: 原始网络连接'), |
| 38 | (r'import\s+.*\bhttp\.client\b', 'import + http.client: HTTP连接'), |
| 39 | (r'import\s+.*\bctypes\b', 'import + ctypes: 调用C函数'), |
| 40 | ] |
| 41 | |
| 42 | HIGH_PATTERNS = [ |
| 43 | (r'^import\s+(?![\w.]+\s*$)', 'import行包含非简单模块名(可能执行代码)'), |
| 44 | (r'import\s+os[;\s]', 'import os: 文件系统/环境变量访问'), |
| 45 | (r'import\s+sys[;\s]', 'import sys: 解释器操作'), |
| 46 | (r'import\s+.*;\s*import', 'import链: 多个import用分号连接'), |
| 47 | (r'import\s+.*\bopen\s*\(', 'import + open(): 文件读写'), |
| 48 | (r'import\s+.*\bgetenv\b', 'import + getenv: 读取环境变量'), |
| 49 | (r'import\s+.*\benviron\b', 'import + environ: 访问环境变量字典'), |
| 50 | ] |
| 51 | |
| 52 | KNOWN_SAFE = {'distutils-precedence.pth','easy-install.pth','setuptools.pth','site.pth'} |
| 53 | |
| 54 | SAFE_IMPORT_PATTERNS = [ |
| 55 | r'^import\s+_distutils_hack\s*$', |
| 56 | r'^import\s+sys;\s*exec\(.*_distutils_hack', |
| 57 | ] |
| 58 | |
| 59 | def _add_venv_site_packages(prefix, paths): |
| 60 | if not os.path.isdir(prefix): return |
| 61 | lib_dir = os.path.join(prefix, 'lib') |
| 62 | if os.path.isdir(lib_dir): |
| 63 | for d in os.listdir(lib_dir): |
| 64 | if d.startswith('python'): |
| 65 | sp = os.path.join(lib_dir, d, 'site-packages') |
| 66 | if os.path.isdir(sp): paths.add(sp) |
| 67 | win_sp = os.path.join(prefix, 'Lib', 'site-packages') |
| 68 | if os.path.isdir(win_sp): paths.add(win_sp) |
| 69 | |
| 70 | def get_site_packages(): |
| 71 | paths = set() |
| 72 | for p in site.getsitepackages(): |
| 73 | if os.path.isdir(p): paths.add(p) |
| 74 | user_site = site.getusersitepackages() |
| 75 | if isinstance(user_site, str) and os.path.isdir(user_site): paths.add(user_site) |
| 76 | if hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix): |
| 77 | venv_site = os.path.join(sys.prefix, 'lib', f'python{sys.version_info.major}.{sys.version_info.minor}', 'site-packages') |
| 78 | if os.path.isdir(venv_site): paths.add(venv_site) |
| 79 | for env_var in ('VIRTUAL_ENV', 'CONDA_PREFIX'): |
| 80 | val = os.environ.get(env_var, '') |
| 81 | if val: _add_venv_site_packages(val, paths) |
| 82 | extra = os.environ.get('PTH_AUDIT_EXTRA_PATH', '') |
| 83 | if extra and os.path.isdir(extra): paths.add(extra) |
| 84 | for venv_name in ('.venv', 'venv', 'env', '.env'): |
| 85 | for base in [os.getcwd(), os.path.expanduser('~')]: |
| 86 | _add_venv_site_packages(os.path.join(base, venv_name), paths) |
| 87 | pyenv_root = os.environ.get('PYENV_ROOT', os.path.expanduser('~/.pyenv')) |
| 88 | versions_dir = os.path.join(pyenv_root, 'versions') |
| 89 | if os.path.isdir(versions_dir): |
| 90 | for v in os.listdir(versions_dir): |
| 91 | _add_venv_site_packages(os.path.join(versions_dir, v), paths) |
| 92 | return sorted(paths) |
| 93 | |
| 94 | def file_sha256(filepath): |
| 95 | h = hashlib.sha256() |
| 96 | with open(filepath, 'rb') as f: |
| 97 | for chunk in iter(lambda: f.read(8192), b''): h.update(chunk) |
| 98 | return h.hexdigest() |
| 99 | |
| 100 | def is_safe_import(line): |
| 101 | for pat in SAFE_IMPORT_PATTERNS: |
| 102 | if re.match(pat, line.strip()): return True |
| 103 | return False |
| 104 | |
| 105 | def scan_pth_file(filepath): |
| 106 | findings = [] |
| 107 | try: |
| 108 | with open(filepath, 'r', errors='replace') as f: lines = f.readlines() |
| 109 | except Exception as e: |
| 110 | findings.append({'severity':'HIGH','message':f'无法读取文件: {e}','line_num':0,'line_content':'','pattern':'read_error'}) |
| 111 | return findings |
| 112 | for i, line in enumerate(lines, 1): |
| 113 | stripped = line.strip() |