nuclear-treestump/pydepgate

GitHub: nuclear-treestump/pydepgate

一款专注于 Python 供应链启动向量的静态安全扫描器,检测 .pth、sitecustomize、__init__.py 等解释器初始化阶段被滥用的恶意代码模式。

Stars: 0 | Forks: 0

# pydepgate [![PyPI](https://img.shields.io/pypi/v/pydepgate.svg)](https://pypi.org/project/pydepgate/)[![Downloads](https://pepy.tech/badge/pydepgate)](https://pepy.tech/project/pydepgate)[![Unit tests](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/100d24e533053216.svg)](https://github.com/nuclear-treestump/pydep-vector-runner/actions/workflows/do_unittests.yml)[![CodeQL Advanced](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/1853e831f1053218.svg)](https://github.com/nuclear-treestump/pydep-vector-runner/actions/workflows/codeql.yml)[![docker-publish](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/e07628bff2053220.svg)](https://github.com/nuclear-treestump/pydep-vector-runner/actions/workflows/docker-publish.yml)[![CodeQL](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/37d9f662ec053222.svg)](https://github.com/nuclear-treestump/pydep-vector-runner/actions/workflows/github-code-scanning/codeql) **一个轻量级的 Python 运行器,用于拦截可疑的启动行为。** pydepgate 会检查 Python 包和环境中那些在解释器启动时静默执行的代码。这正是 2026 年 3 月 LiteLLM 供应链攻击中所使用的攻击手段,并被归类为 [MITRE ATT&CK T1546.018](https://attack.mitre.org/techniques/T1546/018/)。 Screen Recording 2026-04-28 091139 ## 最近新增 ### 递归载荷解码与加密归档输出 `--decode-payload-depth=N` 对解码后的载荷运行递归重新扫描。当 peek 扩展器的解包链产生 Python 源码时,该源码会被重新送入引擎,其中包含的任何载荷发现也会被解码。这可以捕获 LiteLLM 1.82.8 使用的多层攻击形态:外层 base64 载荷解码后的源码中包含第二个 base64 载荷,其解码后才是实际的数据渗出代码。 输出将保存到由 `--decode-location`(默认为 `./decoded/`)指定的目录中,文件命名为 `{STATUS}_{timestamp}_{target}{ext}`,其中 `STATUS` 为 `FINDINGS` 或 `NOFINDINGS`,`timestamp` 是 `YYYY-MM-DD_HH-MM-SS` 格式的 UTC 时间,`target` 是经过净化的构件基本名称。此约定保留了运行历史,并允许按文件名进行按时间顺序的排序。 `--decode-iocs={off,hashes,full}` 控制是否将 IOC 数据和解码源码与报告一并提取。`full` 模式会生成一个加密的 ZIP 归档文件(默认密码为 `infected`,这是恶意软件研究的惯例),其中包含报告、解码源码转储和 IOC 哈希记录,并在归档文件旁生成一个纯文本的 IOC 附带文件,以便于使用 grep 提取哈希。 `--min-severity` 作为展示过滤器流入载荷解码报告中。解码过程本身会对每个包含载荷的发现进行遍历,而不管其严重程度如何,因为低严重程度的外层发现可能会解码出极其严重的内层发现。过滤器随后会修剪生成的树,并应用“保留上下文”规则:如果节点自身的严重程度达到了阈值,或者其任何后代达到了阈值,该节点就会被保留,这样当只有内层发现非常严重时,攻击链也不会被截断。 有关完整的标志列表、输出布局和端到端示例,请参阅下文的“载荷解码”部分。 ### Tab 补全功能已上线! ``` usage: pydepgate completions [-h] {bash,zsh,fish} Print a shell completion script to stdout. Running this command alone does NOT install completion; you have to do something with the output. Quickest install (bash, current shell only): eval "$(pydepgate completions bash)" Persistent install (bash, all future shells): pydepgate completions bash >> ~/.bashrc Persistent install (zsh): pydepgate completions zsh >> ~/.zshrc Persistent install (fish): pydepgate completions fish > ~/.config/fish/completions/pydepgate.fish After installing, open a new shell or re-source your rc file, then test with: pydepgate When run interactively (output to a terminal rather than redirected), this command also prints install instructions to stderr. positional arguments: {bash,zsh,fish} Target shell. Supported: bash, zsh, fish. ``` ## 状态 **静态分析已实现端到端运行。** pydepgate 能够对 wheel 包、sdist 源码包、已安装的包以及单个松散文件进行静态分析,以查找真实世界 Python 供应链攻击中使用的模式。检测范围涵盖载荷编码、动态代码执行、字符串混淆、可疑 stdlib 使用,以及一个广泛的代码密度层,用于捕获混淆、Unicode 欺骗和机器生成的标识符模式。 目前已实现的功能: - 通过 `--single` 对 `.whl` 文件、sdist(`.tar.gz`/`.tgz` 等)、按名称指定的已安装包以及单个松散文件进行静态分析。 - 五个生产级分析器:`encoding_abuse`、`dynamic_execution`、`string_ops`、`suspicious_stdlib` 和 `code_density`。 - 对真实攻击形态的纵深防御。例如,LiteLLM 1.82.8 的 `.pth` 载荷会同时触发四个分析器(ENC001、DYN002、DENS010、DENS011),因此攻击者必须避开每一层才能躲过扫描器。 - 一个根据文件类型和信号上下文提升严重级别的规则引擎,完全通过 TOML 或 JSON 进行数据驱动。默认规则集仅针对密度层信号就包含了 32 条规则。 - 一个安全的局部求值器,无需执行用户代码即可解析混淆的字符串表达式。 - 一个可选的载荷预览扩展器(`--peek`),可以安全地对大型编码字面量进行局部解码,让您无需执行任何内容即可查看其中的内容。支持 base64、hex、zlib、gzip、bzip2 和 lzma 链(可配置深度),对终端载荷进行分类,扫描高信号指标字符串,并在解包链嵌套时发出 ENC002。Pickle 数据会被检测到但永远不会被反序列化;解压炸弹受限于传输中的字节预算。可通过 `--peek-depth`、`--peek-budget`、`--peek-min-length` 和 `--peek-chain`(详细的逐层十六进制转储)进行调整。 - 一个类似 SSH-randomart 的发现分布图,与人类可读的扫描输出一起内联呈现,显示文件中发现的聚集位置及其严重程度。 - 命令行界面,包含 `scan`(包括用于迭代单个文件的 `--single`)和 explain 子命令、环境变量支持、可配置的严重性阈值以及对 CI 友好的输出模式。 - 三种输出格式:人类可读的终端输出、JSON(schema v2)和 SARIF 存根(计划在 v0.4 中实现)。 - 通过 `--color={auto,always,never}`(或 `PYDEPGATE_COLOR`)进行显式颜色控制,并将 `--no-color` 保留为别名。`--color=always` 强制通过管道输出 ANSI 代码,以便与 `less -R` 和支持终端的日志查看器配合使用。 - Pre-commit 钩子集成。将 pydepgate 放入任何 Python 项目的 `.pre-commit-config.yaml` 中,即可在提交时捕获启动向量模式。提供两个钩子 ID:用于 `.py` 文件的 `pydepgate`(默认为 `--min-severity high`,这样信息性发现不会阻止提交)和用于 `.pth` 文件的 `pydepgate-pth`(无严重性过滤;.pth 文件对于 pydepgate 检测的模式没有合法用途)。 - 官方 Docker 镜像位于 `ghcr.io/nuclear-treestump/pydepgate`。多阶段 Alpine 构建大小低于 50 MB,以非 root 身份(uid 1000)运行,为 `linux/amd64` 和 `linux/arm64` 架构发布,并按发行版打标签。可与任何生成 wheel 包的 Python 构建流水线组合使用。 正在积极开发的内容: - comment_analysis 分析器。 - 运行时拦截(`exec` 模式)。 - 环境审计(`preflight` 模式)。 - 别名导入解析(`from subprocess import Popen as P`)。 - pip 包装器 / 传递依赖 `audit` 子命令。 - 用于 GitHub 代码扫描及类似消费者的 SARIF 2.1.0 输出。 [可在 PyPI 上作为 pydepgate 获取](https://pypi.org/project/pydepgate/)。 ## 存在的问题 Python 解释器会在任何用户脚本执行之前,自动在启动时运行几种类型的代码: - `site-packages/` 中的 `.pth` 文件。在解释器初始化期间,任何以 `import` 开头的行都会被 `site.py` 传递给 `exec()` 执行。 - `sitecustomize.py` 和 `usercustomize.py`。如果存在,将被自动导入。 - 任何导入包中的 `__init__.py` 顶层代码。 - `setup.py`。在为源分发版执行 `pip install` 时被执行。 - 控制台脚本入口点。由 `pip install` 生成并执行。 以上每一项都是合法的 Python 功能。但也每一项都曾在真实的供应链攻击中被利用过。现有的 Python 安全工具(`pip-audit`、`safety`、`bandit`)并不检查这些启动向量。 特别是 `.pth` 向量,已被公认为是一个安全漏洞 [CPython issue #113659](https://github.com/python/cpython/issues/113659) 但目前尚无补丁。 ## 安装说明 ``` pip install pydepgate ``` 需要 Python 3.11 或更高版本。无第三方运行时依赖。 ## 使用方法 扫描 wheel 包: ``` pydepgate scan some-package-1.0.0-py3-none-any.whl ``` 扫描源码分发版: ``` pydepgate scan some-package-1.0.0.tar.gz ``` 按名称扫描已安装的包: ``` pydepgate scan litellm ``` 扫描单个松散文件(适用于迭代测试固件、临时检查可疑文件,或复现发现而无需将文件重组为包): ``` pydepgate scan --single suspicious_module.py pydepgate scan --single fixture.pth pydepgate scan --single garbage.py --as init_py ``` `--single` 绕过 wheel/sdist/已安装包的调度过程,直接分析文件。文件类型根据文件名自动检测:`.pth` 文件被视为 `pth`;名为 `setup.py`、`__init__.py`、`sitecustomize.py` 或 `usercustomize.py` 的文件会被分类为其自然类型;其他文件默认为 `setup_py`(最宽松的上下文,非常适合在实际攻击形态严重性下显示每个信号)。使用 `--as` 进行覆盖: `setup_py` / `init_py` / `pth` / `sitecustomize` / `usercustomize`。 解释信号的含义及其触发条件: ``` pydepgate explain STDLIB001 pydepgate explain DENS010 pydepgate explain --rule litellm-pth-stdlib pydepgate explain --list ``` 在 CI 中,使用 `--ci` 获取紧凑的 JSON 输出和正确的退出代码: ``` pydepgate --ci scan some-package.whl ``` 按严重程度过滤发现: ``` pydepgate scan some-package.whl --min-severity high ``` 应用自定义规则文件: ``` pydepgate scan some-package.whl --rules-file company-rules.gate ``` 扫描整个库归档: ``` pydepgate scan --deep somefile.whl ``` 将载荷递归解码到目录中: ``` pydepgate scan --deep some-package.whl --peek \ --decode-payload-depth=3 \ --decode-iocs=full \ --decode-location ./forensics \ --decode-archive-password investigation ``` 这将在 `./forensics/FINDINGS__some-package.whl.zip` 生成加密的归档文件,并在 `./forensics/FINDINGS__some-package.whl.iocs.txt` 生成纯文本的 IOC 附带文件。归档内容将解压到与构件名称匹配的子目录中。有关详细信息,请参阅“载荷解码”部分。 ### 载荷预览 (Payload peek) 某些恶意软件会对其载荷进行压缩或 base64 编码,以绕过简单的字符串匹配扫描器。载荷预览扩展器尝试对大型编码字面量进行安全的局部解码,这样您无需执行它就能查看被标记的代码块中究竟包含什么。默认关闭;可通过 `--peek` 启用。 | 标志 | 环境变量 | 默认值 | 说明 | |---|---|---|---| | `--peek` | `PYDEPGATE_PEEK` | off | 启用扩展器。对带有提示标记的信号运行有界限的解码通道。 | | `--peek-depth N` | `PYDEPGATE_PEEK_DEPTH` | 3 | 最大解包层数。下限为 1,上限为 10。 | | `--peek-budget BYTES` | `PYDEPGATE_PEEK_BUDGET` | 524288 (512 KB) | 所有层的累积输出上限。下限为 1024。 | | `--peek-chain` | `PYDEPGATE_PEEK_CHAIN` | off | 在人类可读输出中提供详细的逐层拆解和 xxd 风格的十六进制转储。 | | `--peek-min-length BYTES` | `PYDEPGATE_PEEK_MIN_LENGTH` | 1024 | 尝试解包前的最小字面量大小。下限为 16。 | 这些作为全局标志,可以在子命令之前或之后使用: ``` pydepgate --peek scan litellm-1.82.8-py3-none-any.whl pydepgate scan litellm-1.82.8-py3-none-any.whl --peek --peek-chain ``` 使用 `--peek` 扫描 LiteLLM 1.82.8 的 wheel 包可以直接暴露嵌入的载荷: ``` [CRITICAL] DENS010 (code_density) in litellm/proxy/proxy_server.py:130:14 string literal at line 130 has Shannon entropy 5.61 bits/char (length 34460) decoded chain: base64 -> python_source (1 layer, 25.2 KB) indicators: subprocess, base64.b64decode ``` 无论是否指定 `--peek-chain`,相同的 `decoded` 块都会出现在 JSON 输出的 `findings[*].context.decoded` 中,包含完整的链、终端分类、指标列表以及未包装字节的十六进制编码预览。有关字段参考,请参阅 `docs/json_schema_v2.md`。 #### 安全保证 预览循环是严格只读的。有三项具体的保证值得明确说明: **Pickle 会被检测到,但永远不会被反序列化。** 当解包循环遇到 Python pickle 流作为终端层时,它会在解码块中设置 `pickle_warning: true` 并停止。对攻击者控制的字节执行 `pickle.loads()` 按照设计就是代码执行,这正是我们要分析的缺陷,而不是工具应该执行的操作。请在隔离环境中使用 `pickletools.dis()`(它在 opcode 流上遍历而不会执行)检查此类载荷。 **解压炸弹受到限制。** 所有解包层的累积输出受 `--peek-budget` 上限限制。一个本来会扩展到 2 GB 的 2 zlib 流会在 512 KB(默认值)处触发上限,记录 `unwrap_status: exhausted_budget`,然后停止。该上限通过增量 `decompressobj.decompress(data, max_length=N)` 调用在传输中强制执行,超过预算的字节永远不会被实体化。 **ENC002 在嵌套链上触发。** 当解包链达到深度 2,或者在仍有更多转换可能的情况下耗尽了 `--peek-depth`,扩展器将发出一个 `ENC002` 信号,携带解码块和链摘要。单层 base64 是平淡无奇的,它是证书、token 和配置块的通用语言。而堆叠的层(`base64 → zlib → python_source`)则代表了恶意意图。ENC002 的默认严重程度因文件类型和未包装状态而异;完整表格请参阅 `pydepgate.rules.defaults`。 ### 退出代码 - `0` 干净。无发现(或没有高于 `--min-severity` 的发现)。 - `1` 存在发现,但没有 HIGH 或 CRITICAL。 - `2` 至少有一个 HIGH 或 CRITICAL 的发现。 - `3` 工具错误。pydepgate 无法完成扫描。 这些退出代码作为 v0.1+ 契约的一部分将保持稳定。 ### 环境变量 所有标志都可以通过环境变量设置。显式标志会覆盖环境变量值。 | 变量 | 等效标志 | |---|---| | `PYDEPGATE_CI` | `--ci` | | `PYDEPGATE_FORMAT` | `--format` | | `PYDEPGATE_NO_COLOR` (或 `NO_COLOR`) | `--no-color` | | `PYDEPGATE_MIN_SEVERITY` | `--min-severity` | | `PYDEPGATE_STRICT_EXIT` | `--strict-exit` | | `PYDEPGATE_RULES_FILE` | `--rules-file` | | `PYDEPGATE_PEEK` | `--peek` | | `PYDEPGATE_PEEK_DEPTH` | `--peek-depth` | | `PYDEPGATE_PEEK_BUDGET` | `--peek-budget` | | `PYDEPGATE_PEEK_CHAIN` | `--peek-chain` | | `PYDEPGATE_PEEK_MIN_LENGTH` | `--peek-min-length` | | `PYDEPGATE_DECODE_PAYLOAD_DEPTH` | `--decode-payload-depth` | | `PYDEPGATE_DECODE_LOCATION` | `--decode-location` | | `PYDEPGATE_DECODE_FORMAT` | `--decode-format` | | `PYDEPGATE_DECODE_IOCS` | `--decode-iocs` | | `PYDEPGATE_DECODE_ARCHIVE_PASSWORD` | `--decode-archive-password` | `--decode-archive-stored` 故意没有对应的环境变量;这是一个每次调查独立做出的选择(当字节可验证的归档内容对该特定调查很重要时使用 STORED),而不是一个持久的首选项。 ## pydepgate 检测什么 当前的分析器集涵盖了启动向量中五大类可疑行为: **编码滥用 (ENC001, ENC002)。** 在单个链中解码并执行编码内容的模式,例如 `exec(base64.b64decode(payload))`。可捕获 base64、hex、编解码器、zlib、bz2、lzma 和 gzip 变体。启用 `--peek` 后,当局部解码器的解包循环达到 2 层以上或耗尽了配置的深度时,ENC002 也会触发,这是一个强有力的证据,表明某个字面量被蓄意混淆,而不是一个无害的编码数据块。 **动态执行 (DYN001-007)。** 直接调用 `exec`、`eval`、`compile` 或 `__import__`;通过 `getattr`、`globals()`、`locals()`、`vars()` 或 `__builtins__` 下标访问执行原语;跨文件的先编译后执行;以及能捕获 `e = exec; e(...)` 逃避手法的别名调用形态。 **字符串混淆 (STR001-004)。** 解析结果为执行原语名称、危险 stdlib 函数或敏感模块名称的混淆字符串表达式。使用安全的局部求值器静态计算表达式会生成什么字符串,而无需执行用户代码。可捕获: - 拼接:`'ev' + 'al'` - 字符编码:`chr(101) + chr(118) + chr(97) + chr(108)` - 切片:`'lave'[::-1]` - 字面量片段的 `str.join`:`''.join(['e','v','a','l'])` - `bytes.fromhex('6576616c').decode()` - 带有字面量插值的 f-string 拼接 - 包含混淆值的单一赋值变量 **可疑的 stdlib 使用 (STDLIB001-003)。** 调用在启动向量中极不寻常的 stdlib 函数: - `STDLIB001`:进程生成(`os.system`、`subprocess.Popen`、`subprocess.run`、`os.exec*` 等) - `STDLIB002`:网络操作(`urllib.request.urlopen`、`socket.socket`、`http.client` 等) - `STDLIB003`:本机代码加载(`ctypes.CDLL`、`ctypes.WinDLL` 等) 默认置信度为 `HIGH`。当这些调用出现在 `setup.py` 或 `.pth` 文件中(它们在这里没有任何存在的合理业务)时,规则引擎会将其提升为 `CRITICAL`。这正是针对 LiteLLM 1.82.8 触发的规则。 “隐藏得越深,信号越强”模型是通过操作计数实现的:一个需要经过多次混淆操作才能拼凑出敏感名称的表达式,比只需少数操作的表达式更确信是恶意的。 **代码密度 (DENS001-051)。** 一个广泛的层,用于捕获混淆代码的外观特征,即使单个原语调用本身并不可疑。五个子层中的十三个独立信号: *词法(行形状):* - `DENS001`:单行 token 压缩(压缩或捆绑器模拟形状) - `DENS002`:在一行上的分号链式多语句 *字符串内容:* - `DENS010`:高熵字符串字面量(香农熵与 base64、压缩或加密内容一致) - `DENS011`:仅使用 base64 字母表字符的字面量,即使没有伴随的解码调用 *标识符形状:* - `DENS020`:低元音比率标识符(机器生成或故意篡改的名称,如 `_xkjwbq`) - `DENS021`:容易混淆的单字符标识符(`l`、`O`、`I`) *Unicode:* - `DENS030`:源码中不可见的 Unicode 字符(零宽度空格、RTL 覆盖;被归类为 [CVE-2021-42574](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574) 的特洛伊源码攻击类别) - `DENS031`:标识符中的 Unicode 同形字(用于逃避字符串匹配扫描器的西里尔文和希腊文形似字) *结构性:* - `DENS040`:AST 深度与行数不成比例(隐藏在表达式树中的压缩) - `DENS041`:深度嵌套的 lambda 或推导式(函数式风格混淆) - `DENS042`:大字节范围整数数组(包含 122 个 0-255 整数的列表,属于 shellcode 暂存形状) *文档字符串:* - `DENS050`:高熵文档字符串(文档字符串作为载荷的走私模式) - `DENS051`:动态 `__doc__` 引用被传递给可调用对象(走私模式的运行时解码并执行半部) 默认规则集附带 32 条规则,涵盖五种文件类型的这些信号,并经过校准,使得相同内容的扫描方式会因其所在位置而异。`.pth` 中的高熵 base64 字面量为 `CRITICAL`(没有合法的用例);在 `__init__.py` 中的相同字面量为 `MEDIUM`(某些包确实会合法地附带编码块);在其他任何地方的相同字面量为 `LOW`(UUID 和哈希很常见)。`DENS021` 普遍为 `INFO`,因为类似 PEP-8 的易混淆字符本身并不是安全发现;它们只在其他信号触发时作为贡献信号发挥作用。 ## 实践中的分层检测 LiteLLM 1.82.8 的 `.pth` 载荷只有单行: ``` import base64; exec(base64.b64decode('cHJpbnQoMSkK')) ``` 只 grep 搜索 `exec` 的扫描器会抓住它。只 grep 搜索 `base64.b64decode` 的扫描器也会抓住它。但是,如果攻击者知道这两种检测手段中的任何一种,都可以轻松击败它们。pydepgate 在这一行上从四个独立的分析器中触发了五个独立的发现: - **ENC001** (encoding_abuse):解码后执行模式 - **DYN002** (dynamic_execution):在模块作用域中带有非字面量参数的 `exec()` - **DENS001** (code_density):token 密集的单行 - **DENS010** (code_density):高熵字符串字面量 - **DENS011** (code_density):base64 字母表字符串字面量 此外,由于该文件是 `.pth`,规则层将它们全部提升为 `CRITICAL`。为了逃避 pydepgate,攻击者必须同时击败每个分析器,同时还要生成一个有效的 `.pth` 载荷。每次逃避都会缩小可能性;所有逃避的交集对于任何可以在 Python 启动时实际执行的形态来说都是空集。 ## 规则引擎 分析器发出原始信号。规则引擎使用数据驱动的规则集将信号映射为具有严重性级别的发现。默认规则以 JSON 格式附带;用户可以使用项目根目录、venv 根目录中的 `pydepgate.gate` 文件(自动检测 TOML 或 JSON)或通过 `--rules-file` 指定的文件来覆盖或扩充它们。 一条规则是一个小型的结构化对象: ``` { "id": "litellm-pth-stdlib", "match": { "signal_id": "STDLIB001", "file_kind": "pth" }, "actions": [ {"type": "set_severity", "severity": "critical"} ] } ``` 支持三种操作:`set_severity`、`suppress` 和 `set_description`。无论特异性如何,用户规则始终优先于默认规则。被抑制的发现会被单独跟踪,以便用户可以看到哪些内容本应触发以及为什么没有触发。 运行 `pydepgate explain --list` 查看所有默认规则和信号,以及有关它们捕获的内容和规则如何提升它们的说明。 ### 载荷解码 某些攻击将其载荷隐藏在多个解码阶段之后。预览向您展示了第一层解码后的样子,但如果该层本身是包含另一个编码块的 Python 源码,预览会将其视为终端并停止。解码过程从预览停止的地方接续:它获取预览解码出的字节,对其重新运行分析器引擎,并对内部扫描发现的任何包含载荷的发现进行递归。结果是一棵反映发现路径的树。每个节点记录了解码了什么、在里面发现了什么以及递归在哪里停止。 此过程通过 `--decode-payload-depth=N` 选择性启用。它需要 `--peek`(解码驱动器使用预览的输出,因此预览必须首先运行)。所有标志值都可以通过环境变量设置,遵循与现有预览环境变量相同的优先级规则;显式标志会覆盖环境变量值。 | 标志 | 环境变量 | 默认值 | 说明 | |---|---|---|---| | `--decode-payload-depth N` | `PYDEPGATE_DECODE_PAYLOAD_DEPTH` | 3 | 最大递归深度。下限为 1,上限为 8。需要 `--peek`。 | | `--decode-location PATH` | `PYDEPGATE_DECODE_LOCATION` | `./decoded/` | 输出目录。内部文件遵循 `{STATUS}_{timestamp}_{target}{ext}` 命名约定;如果目录不存在则会被创建。 | | `--decode-format FMT` | `PYDEPGATE_DECODE_FORMAT` | `text` | `text` 用于人类可读的树状报告;`json` 用于适合下游工具的结构化表示。 | | `--decode-iocs MODE` | `PYDEPGATE_DECODE_IOCS` | `off` | `off`、`hashes` 或 `full`。详见下文的完整模式矩阵。 | | `--decode-archive-password PASSWORD` | `PYDEPGATE_DECODE_ARCHIVE_PASSWORD` | `infected` | 由 `--decode-iocs=full` 生成的加密归档的密码。默认的 `infected` 是恶意软件研究领域的惯例,被防病毒厂商识别为“不扫描”标记。ZipCrypto 在密码学上已被破解;这不是一种保密控制手段。 | | `--decode-archive-stored` | (无) | off | 对归档使用 STORED 压缩而不是 DEFLATE。生成的归档文件稍大,但完全绕过了 zlib。当字节可验证的归档内容很重要时非常有用。 | #### 输出文件命名约定 所有解码输出文件都使用一致的命名模式: ``` {STATUS}_{timestamp}_{target}{extension} ``` 当解码树包含节点时,`STATUS` `FINDINGS`,否则为 `NOFINDINGS`。`timestamp` 是 UTC 时间,格式为 `YYYY-MM-DD_HH-MM-SS`,没有 `Z` 后缀;该格式可按字典顺序排序且没有时区歧义。`target` 是构件标识的基本名称(例如 wheel 文件名),净化为 `[A-Za-z0-9._-]` 并去除前导分隔符。`extension` 对于文本输出为 `.txt`,JSON 输出为 `.json`,加密归档为 `.zip`,或者纯文本 IOC 附带文件为 `.iocs.txt`。 此约定保留了运行历史。重新运行相同的命令会生成一个带有新时间戳的新文件;之前的文件保持不变。使用 `ls -t /` 查找最新的输出。 #### IOC 模式矩阵 `--decode-iocs` 的三种模式产生不同的输出形状: **`off` (默认)。** 仅包含树报告的单个纯文本文件。没有 IOC 哈希,没有解码源码转储,没有归档。当解码树为空时,不写入任何文件;您会收到一条 stderr 提示。适用于快速的交互式分类。 **`hashes`。** 两个纯文本文件:树报告(与关闭模式相同)和一个后缀为 `.iocs.txt` 的附带文件,其中包含每个解码载荷的 SHA256/SHA512 哈希值,以及每个载荷的链摘要和位置。哈希记录使用固定的双 token 行形状(`decoded_sha256 `),因此像 `grep '^decoded_sha256' iocs.txt | awk '{print $2}'` 这样的单行命令可以提取每个哈希以进行批量查找。对于空树将跳过,与关闭模式相同。 **`full`。** 一个加密的 ZIP 归档外加一个纯文本 IOC 附带文件。归档在与净化的目标名称匹配的子目录内包含三个文件: ``` /report.txt Tree report (same shape as off-mode output) /sources.txt Per-layer decoded source dumps with header blocks and line-numbered bodies /iocs.txt Hash records (same content as the sidecar) ``` 附带文件位于归档文件旁边,不在其中,因此您无需解压即可批量处理 IOC 哈希。与关闭和哈希模式不同,完整模式始终会产生输出:空树会生成一个 NOFINDINGS 存根归档,具有相同的结构但内部是存根标记。这种一致性让下游分类工具可以将归档的存在作为扫描已完成的信号。 裸形式 `--decode-iocs`(无值)被接受为 `--decode-iocs=hashes` 的已弃用同义词,并会发出弃用警告。请使用显式形式以避免该警告。 #### --min-severity 交互 `--min-severity` 在解码完成后作为展示过滤器应用。解码过程本身不受严重程度限制,因为低严重程度的外层发现可能会解码出极其严重的内层发现;在输入层进行限制将跳过外层从而丢失严重的内层。过滤器在生成的树上运行一次,并应用“保留上下文”规则: - 如果节点自身的严重性达到阈值,或者其任何后代达到阈值,该节点将被保留。 - 叶子子发现(非递归的)被严格过滤:如果它们自身的严重性低于阈值,则会被丢弃,无论父级如何。 因此,一个解码出严重内层的低严重性外层会保留在报告中,并且显示您如何从外层到达严重内层的链也会被保留。一个后代全都是低严重性的低严重性外层则会被丢弃。 #### 实际示例 使用完整归档处理方式扫描 LiteLLM 1.82.8 wheel 包: ``` pydepgate scan --deep litellm-1.82.8-py3-none-any.whl \ --peek --peek-chain \ --min-severity high \ --decode-payload-depth=4 \ --decode-iocs=full \ --decode-location ./forensics \ --decode-archive-password investigation ``` 在 `./forensics/` 中生成: ``` FINDINGS_2026-04-29_21-57-15_litellm-1.82.8-py3-none-any.whl.zip FINDINGS_2026-04-29_21-57-15_litellm-1.82.8-py3-none-any.whl.iocs.txt ``` 验证并检查: ``` unzip -P investigation -l ./forensics/FINDINGS_*.zip # 应列出:/report.txt, /sources.txt, /iocs.txt unzip -P investigation -d /tmp/extracted ./forensics/FINDINGS_*.zip less /tmp/extracted/litellm-1.82.8-py3-none-any.whl/report.txt # 从 sidecar 快速提取 hash: grep '^decoded_sha256' ./forensics/FINDINGS_*.iocs.txt | awk '{print $2}' ``` #### 安全保证 解码过程继承了预览扩展器的所有安全属性(pickle 检测但不反序列化、解压炸弹预算、传输中的上限强制执行),并增加了两项。 **加密归档是为了防病毒软件友好,而非为了保密。** ZipCrypto 在密码学上已被破解(已知明文攻击可在几秒钟内恢复密码)。默认密码 `infected` 是恶意软件研究的惯例;防病毒厂商识别带有该密码的归档为“不扫描”对象,并且不会在调查中途隔离它们。这种加密保护您免受自己防病毒软件的影响,而不是防范任何其他对手。请像对待任何恶意软件样本一样对待归档内容。 **解码过程永远不会执行解码后的内容。** 重新扫描解码后的字节与扫描原始构件一样经过相同的引擎路径,并且该引擎自 v0.1 以来一直是相同的只读分析器管道。解码后的 Python 源码通过 `ast.parse` 解析(没有编译,没有执行),并且其中的任何带有信号的字面量本身都被视为数据而不是代码,无论我们深入了多少个解码层。 ## 编写规则 规则存放在 `pydepgate.gate` 文件中。格式为 TOML 或 JSON;pydepgate 会根据内容自动检测。一条规则由三个部分组成:身份(`id`)、匹配项(适用于哪些信号)和操作(匹配时执行什么)。 ### 发现 当您运行 `pydepgate scan` 时,将从以下第一个匹配项加载规则: 1. `--rules-file` CLI 标志(如果给定)。 2. `PYDEPGATE_RULES_FILE` 环境变量。 3. 当前目录下的 `./pydepgate.gate`。 4. 活跃虚拟环境中的 `/pydepgate.gate`(如果有)。 如果存在多个文件,则仅加载第一个。其他文件会列在扫描摘要中,以便您查看跳过了什么。 ### 最小规则 (TOML) ``` [[rule]] id = "my-package-uses-large-base64" signal_id = "DENS010" path_glob = "my_package/embedded/*.py" action = "suppress" explain = "We legitimately ship a 200KB embedded model in this dir." ``` `id` 由您定义。`signal_id` 是要匹配的内容(有关目录请参阅 `pydepgate explain --list`)。`path_glob` 是与文件内部路径匹配的 fnmatch 风格的模式。`action` 是 `set_severity`、`suppress` 或 `set_description` 中的一种。`explain` 是可选的,但鼓励使用:它会显示在 `pydepgate explain --rule USER_my-package-uses-large-base64` 中。 ### 匹配条件 对于应用某条规则,必须满足所有非空的匹配字段。支持的字段: | 字段 | 匹配目标 | |----------------------|------------------------------------------------------| | `signal_id` | `Signal.signal_id` (例如 `"DENS010"`) | | `analyzer` | `Signal.analyzer` (例如 `"code_density"`) | | `file_kind` | 分类决策:`pth`、`setup_py`、`init_py`、`sitecustomize`、`library_py` 等 | | `scope` | `Signal.scope`:`module`、`function`、`class` | | `path_glob` | 与文件内部路径匹配的 fnmatch 模式 | | `context_contains` | 必须以严格相等出现在 `Signal.context` 中的 `{key: value}` 字典对 | | `context_predicates` | 针对 `Signal.context` 计算的 `{key: {operator: value}}` 字典对(比 `context_contains` 更丰富,见下文) | ### 上下文谓词 `context_predicates` 使用比较运算符扩展了 `context_contains`。每个谓词采用 `{field: {op: value}}` 的形式。内部字典必须刚好包含一个运算符键。不同字段上的多个谓词之间是 AND 关系。 ``` # 拦截任何大小为 10KB 或以上的 base64 形状字符串 [[rule]] id = "block-large-base64" signal_id = "DENS010" context_predicates = { length = { gte = 10240 } } action = "set_severity" severity = "critical" # 仅在测试文件中禁止容易混淆的单字符标识符 [[rule]] id = "ignore-confusables-in-tests" signal_id = "DENS021" path_glob = "tests/**/*.py" context_predicates = { identifier = { in = ["l", "O", "I"] } } action = "suppress" ``` 可用的运算符: | 类别 | 运算符 | 值类型 | |------------|--------------------------------------------|------------------------| | 数值 | `eq`, `ne`, `gt`, `gte`, `lt`, `lte` | int 或 float | | 字符串 | `eq`, `ne`, `contains`, `startswith`, `endswith` | string | | 集合 | `in`, `not_in` | list, tuple 或 set | 类型不匹配(例如对字符串使用 `gte`)会导致谓词静默匹配失败,而不是抛出错误。要对同一字段进行 AND 多重条件判断,请编写多条规则。 ### 等效的 JSON ``` { "_pydepgate_format": "json", "_pydepgate_version": 1, "rules": [ { "id": "block-large-base64", "signal_id": "DENS010", "context_predicates": {"length": {"gte": 10240}}, "action": "set_severity", "severity": "critical" } ] } ``` ### 操作 - **`set_severity`**:需要一个 `severity` 字段(`info`、`low`、`medium`、`high`、`critical`)。 - **`suppress`**:从扫描输出中丢弃该发现。抑制操作仍会被记录;`pydepgate scan -v` 会显示被抑制的内容以及由哪条规则执行的抑制。 - **`set_description`**:需要一个 `description` 字段;替换发现的文本。 ### 优先级 当多条规则匹配到同一个信号时,pydepgate 会按以下顺序挑选一个胜出者: 1. 来源优先级:无论特异性如何,用户规则胜过系统规则,系统规则胜过默认规则。 2. 特异性:在相同来源的规则中,匹配字段越多者胜出。每个 `context_predicates` 条目算作一个匹配字段。 3. 加载顺序:在来源和特异性相同的情况下,较早出现的规则胜出。 这意味着与默认规则具有相同形态的用户 `[[rule]]` 总是会胜出。如果您希望您的规则输给更具体的默认规则,请添加比您要覆盖的规则更少的匹配字段。 ### 验证 规则在加载时会进行验证。错误会被累积并一起报告;如果任何规则验证失败,整个文件将被拒绝(不加载任何规则)。常见错误: - 未知字段名:`Did you mean 'context_contains'?` - 未知运算符:`Did you mean 'gte'?` - 一个谓词中包含多个运算符:每个字段必须刚好包含一个。 - 对于 `set_severity` 操作缺少 `severity` 字段。 编辑后运行一次 `pydepgate scan --rules-file my.gate` 以确认所有内容均能解析成功。 ## 设计约束 - **零运行时依赖。** 仅使用标准库。这是一个承重设计约束,而不是风格偏好:对于一款职责是防御供应链攻击的工具而言,每增加一个依赖项都会增加供应链攻击面。 - **构造即安全。** 解析器和局部求值器从不执行、编译或导入输入内容。解析器建模的每一个操作都是仅使用 Python 内置函数对解析器自身产生的值从零开始重新实现的。 - **引导时的自完整性。** 关键的 stdlib 引用在任何不受信任的代码运行之前被捕获到局部变量中(当运行时引擎在 v0.4 中发布时,这一点将变得相关)。 - **轻量级。** 完整的测试套件在现代笔记本电脑上大约七秒钟即可运行完毕,包括针对已安装包的基于子进程的 CLI 测试。 ## 与 PyDepGuard 的关系 pydepgate 是一个专注于启动向量拦截的单一用途狭窄工具。[PyDepGuard](https://github.com/nuclear-treestump/pylock-dependency-lockfile) 是一个涵盖运行时沙箱和依赖管理的更广泛的 Python 安全框架。在 pydepgate 中开发的启动向量引擎最终计划将作为子系统整合到 PyDepGuard 中;在此之前,这两个项目独立开发。 只需要启动向量保护的用户应该使用 pydepgate。需要完整运行时安全模型的用户应直接使用 PyDepGuard。 ## 架构 代码库被组织为一个分层管道: ``` parsers/ bytes -> structured representations (pth, pysource, wheel, sdist) introspection/ installed package enumeration via importlib.metadata traffic_control/ path-based triage; decides what to analyze analyzers/ structured representations -> raw signals _resolver.py safe partial evaluator (shared infrastructure) _visitor.py scope tracking and AST utilities (shared) encoding_abuse ENC001 dynamic_execution DYN001-007 string_ops STR001-004 suspicious_stdlib STDLIB001-003 density_analyzer DENS001-051 enrichers/ signal hints -> enriched signal context _magic.py magic-byte tables and ASCII-alphabet predicates _unwrap.py bounded multi-layer decode/decompress loop payload_peek ENC002 emission and decoded context block rules/ signals + context -> severity-rated findings base.py rule data model and matching logic defaults.py default rule set (90+ rules across all signals) loader.py TOML/JSON parser with validation and typo suggestions explanations.py structured explain-output content engines/ orchestration (currently: static) visualizers/ inline rendering helpers for the human reporter density_map SSH-randomart-style finding-distribution renderer cli/ argparse, dispatch, reporters, explain subcommand ``` 分析器看不到原始字节。它们遍历解析后的表示并发出 `Signal` 对象。规则引擎用严重性包装信号以生成 `Finding` 对象,按优先级顺序应用用户和默认规则。CLI 以人类可读格式、JSON 或 SARIF 格式呈现发现。 `_resolver.py` 模块是可重用的基础设施,适用于任何需要知道表达式求值结果的分析器。它返回结构化的 `ResolutionResult` 对象,其中包含成功/失败状态、操作计数、部分值和已解析的片段列表。 静态引擎暴露了三个用于单文件分析的入口点。`scan_file(path)` 读取字节并根据文件名路由进行分类。`scan_bytes(content, internal_path, ...)` 是按文件处理的主力,构件枚举器(wheel、sdist、已安装包)对每个范围内文件调用一次。`scan_loose_file_as(path, file_kind)` 完全绕过分类并强制指定文件类型,将真实路径保留至发现上下文中;这是由 `pydepgate scan --single` 使用的入口点。 ## 开发 ``` git clone https://github.com/nuclear-treestump/pydep-vector-runner cd pydep-vector-runner pip install -e . python -m unittest discover tests -v ``` 随着分析器集的扩展,测试套件已增长到大约 500 个测试。测试按模块组织,包括快乐路径覆盖、逃避测试集、误报测试集、针对对抗性输入的鲁棒性检查、针对合成 wheel 包和 sdist 的集成测试,以及通过子进程进行的 CLI 测试。 要在编辑后重新生成二进制 `.pth` 测试固件: ``` python scripts/generate_fixtures.py ``` ## 安全提示 本项目构建用于防御 Python 供应链攻击的工具。`tests/fixtures/` 中的测试固件和集成测试中使用的合成样本仅模拟已知攻击(LiteLLM 1.82.8、特洛伊源码 CVE-2021-42574,以及在 T1546.018 下编目的其他攻击)的*结构形态*,但包含的是惰性载荷。本代码仓库中不存在实际的恶意代码。 要真实恶意样本进行回归测试,请使用 [OSSF malicious-packages](https://github.com/ossf/malicious-packages)、[Datadog malicious-software-packages-dataset](https://github.com/DataDog/malicious-software-packages-dataset) 或 [lxyeternal/pypi_malregistry](https://github.com/lxyeternal/pypi_malregistry) 数据集。请在一次性的虚拟机或容器中执行此操作,并且不要将样本提交到本代码仓库。 ## 已知限制 pydepgate 的静态分析对其能够和无法捕获的内容秉持坦诚态度。记录在案的差距包括: **分析差距:** - 函数返回值跟踪。对于 `code = make_payload()`,如果 `make_payload()` 内部调用了 `compile(...)`,则不会被标记。 - `__builtins__` 作为 Name 下标(而不是通过函数调用)。 - 解析器变量跟踪中的元组解包、增强赋值和条件赋值。 - Lambda 作用域精度(lambda 被视为其封闭作用域)。 - 别名 stdlib 导入,如 `from subprocess import Popen as P`。 - 暂时如此。我很快就会加上这个。 **密度层注意事项:** - `DENS020`(低元音比率标识符)和 `DENS040`(AST 深度)都会在合法的机器生成代码(Cython 输出、解析器表、生成的配置)上产生误报。它们在启动向量之外以 `LOW` 严重级别发布,因此它们会作为贡献信号浮出水面,而不是独立的警报。 - `DENS031`(同形字)可能会在非拉丁语代码库中对合法的非英文字母变量名触发。默认规则在启动向量之外将其保持在 `HIGH` 而不是 `CRITICAL`,以便故意使用非拉丁语命名的用户可以通过单条用户规则予以抑制。 ## 作者 Ikari ([@0xIkari](https://github.com/0xIkari)) ## 许可证 Apache 2.0。详见 [LICENSE](LICENSE)。
标签:Base64解密, Google AI, Homebrew安装, LiteLLM, payload解码, PyPI, Python安全, 云安全监控, 加密分析, 启动行为监控, 多包管理, 环境安全, 自动化payload嵌入, 请求拦截, 运行器, 静态分析