facebook/Lifeguard

GitHub: facebook/Lifeguard

一款基于 Rust 的 Python 静态分析器,通过检测与懒加载不兼容的代码模式来帮助项目安全采用 PEP 810 懒加载特性。

Stars: 83 | Forks: 7

# 懒加载的救生员 一款快速的静态分析工具,旨在帮助你在 Python 中采用[懒加载](https://peps.python.org/pep-0810/)。 ## 什么是懒加载? 在 Python 中,每个 `import` 语句都会在模块加载时立即执行。无论该导入是否实际被使用,都会产生这种开销。[PEP 810](https://peps.python.org/pep-0810/) 为 Python 引入了*显式懒加载*,它将模块的实际加载推迟到首次访问该导入名称时进行。懒加载可以显著减少内存使用、启动时间和导入开销,特别是在具有深层依赖树的大型代码库中。 然而,一些 Python 模式依赖于导入立即执行。例如: - **模块级副作用** — 如果导入被推迟,在导入时注册处理程序或修改全局状态的模块的行为会有所不同。 - **注册表模式** — 如果在导入时注册自身(例如,添加到全局字典中)的模块在懒加载下会静默注册失败。 - **`sys.modules` 操作** — 读取或写入 `sys.modules` 的代码会假设先前的导入已经执行。 - **元类和 `__init_subclass__`** — 类创建的副作用可能依赖于导入已被解析。 将现有代码库调整为使用懒加载可能是一项艰巨的任务,特别是在大规模项目中。Lifeguard 能够识别这些不兼容的模式,让你可以放心地采用懒加载。 ## Lifeguard 是如何工作的? Lifeguard 会并行分析给定项目的 Python 源文件。它会遍历每个模块的 AST 以检测副作用,并将与懒加载不兼容的副作用映射为错误。分析器在分析时采取了保守的策略:任何无法通过程序确定可以安全进行懒加载的模块,默认情况下都会被标记为不安全。 这意味着 Lifeguard 会倾向于将潜在兼容的模块标记为不兼容,从而为了生产环境的安全而放弃潜在的性能优化。 有关分析管道和架构的更深入了解,请参阅 [docs/architecture.md](docs/architecture.md)。 ## 项目阶段:Beta Lifeguard 正在积极开发中。我们的目标是在 [Python 3.15 最终版本](https://peps.python.org/pep-0790/)发布前做好通用准备。 ### 我们路线图上的项目 - 我们正在准备 GitHub Actions,以全面支持外部贡献者。 - 我们计划发布到 [PyPI](https://pypi.org/)。 - 我们已经测试并支持 Python 3.12 和 3.14。其他版本也可能适用。我们目前尚未支持 [PEP 810 中新增的 `lazy` 关键字](https://peps.python.org/pep-0810/) —— 但我们完全打算在 3.15 版本发布前支持它。 - 我们正在积极开发一个独立的 linter 输出模式,以帮助用户识别其代码库中哪些特定行与懒加载不兼容。 - 我们计划增加对 Lifeguard 输出的轻松导入支持,以驱动高级用户启用懒加载(参见[使用输出](#using-the-output))。 ## 前置条件 - **Rust (nightly)** — 该 crate 使用了 unstable 特性。通过 [rustup](https://rustup.rs/) 安装并使用 `rustup default nightly` 进行设置。 - **Git** — 使用子模块进行克隆:`git clone --recurse-submodules https://github.com/facebook/Lifeguard.git` 如果你在克隆时没有使用 `--recurse-submodules`,请运行 `git submodule update --init --recursive`。 ## 快速开始 体验 Lifeguard 最快的方式是使用 `run-tree` 子命令,它会分析目录下的每一个 `.py` 文件。无需额外设置。 ``` cargo run -- run-tree ``` 例如,使用内置的示例项目: ``` cargo run -- run-tree testdata/sample_project output.json ``` 有关包括解读输出在内的完整演练,请参阅 [GETTING_STARTED.md](GETTING_STARTED.md)。 ## 运行 Lifeguard 对于需要更多控制的大型项目,你可以生成一个*源数据库*(source DB)—— 一个 JSON 文件,用于告诉 Lifeguard 你项目中 Python 文件的完整集合及其模块路径(详情请参见[输入格式](#input-format))。请按照以下步骤操作: 1. 生成源数据库。我们提供了一个子命令来为你生成此文件,但你可能需要手动进行调整。(随着项目的成熟,我们希望使这个过程更加顺畅。) ``` cargo run -- gen-source-db ``` (可选)如果你的项目有库依赖,你可以通过在 `pyproject.toml` 中添加 `lifeguard` 部分,将 Lifeguard 指向你的 site-packages: ``` [lifeguard] site_packages = "/path/to/site-packages" ``` 你可以通过 `python -m site` 找出你的 site-packages 路径。`gen-source-db` 子命令在生成源数据库时会自动读取此部分。 **注意:** 该脚本可能无法发现你项目的所有依赖项。如果 Lifeguard 报告缺少模块,你可能需要手动向生成的源数据库中添加条目。 2. 在两种模式之一下运行 Lifeguard: - **默认模式**:打印代码库的高层级分析(兼容文件的百分比、主要错误等),并将 JSON 输出写入 `OUTPUT_PATH`。 cargo run -- - **详细模式**:还会生成一份人类可读的报告,显示每个模块中导致不兼容的具体代码行。 cargo run -- --verbose-output **详细输出示例:** ``` ## example.module.foo ### 错误 Line 17 - ImportedModuleAssignment sys Line 38 - UnsafeFunctionCall example.demo.unsafe_method ``` ## 输入格式 在某些模式下,Lifeguard 需要一个源数据库 —— 一个将 Python 模块路径映射到其在磁盘上位置的 JSON 文件。其格式如下: ``` { "build_map": { "foo/bar.py": "/local/usr/disk/foo/bar.py", "example/__init__.py": "/local/usr/disk/third-party/example/__init__.py" } } ``` 你可以使用 `cargo run -- gen-source-db` 自动生成此文件(参见[运行 Lifeguard](#running-lifeguard)),或者手动创建。 ## 输出格式 Lifeguard 会写入一个包含两个字段的 JSON 文件: ``` { "LAZY_ELIGIBLE": { "module1": [], "module2": ["module3", "module4"], "module5": [], }, "LOAD_IMPORTS_EAGERLY": ["module5", "module99", "module100"] } ``` ### `LAZY_ELIGIBLE` 一个字典,将适合懒加载的模块映射到必须被立即导入的依赖项列表。例如: - `"module1": []` — `module1` 完全可以安全地进行懒加载,没有任何限制。 - `"module2": ["module3", "module4"]` — `module2` 可以安全地进行懒加载,**但前提是** `module3` 和 `module4` 已经被导入。 **重要提示:** *未*作为键出现在此字典中的模块,在分析后被视为不适合懒加载。 ### `LOAD_IMPORTS_EAGERLY` 一个模块集合,其中该模块内的*所有*导入都必须被立即加载。对于这些模块,懒加载实际上被暂时禁用了。 **注意区别:** 其他模块仍然可以懒加载 `LOAD_IMPORTS_EAGERLY` 集合中的模块,但是当该模块被加载时,其自身的 `import` 语句必须立即执行,而不是被推迟。 此集合仅用于特定的极端情况: - **自定义 finalizer** (`__del__`) — 不可预测的执行时机意味着导入必须在终结时可用。 - **`exec()` 调用** — 动态代码执行会使静态分析的保证失效。 - **`sys.modules` 访问** — 读取或写入 `sys.modules` 可能依赖于先前导入的已经执行。 有关更多详细信息,请参阅 [docs/load_imports_eagerly.md](docs/load_imports_eagerly.md)。 ## 使用输出 ### 作为独立的 linter Lifeguard 可以用作独立的 linter,以识别你代码库中哪些特定行与懒加载不兼容。使用 `--verbose-output` 运行分析器,即可获得一份人类可读的报告,其中包含每个模块的错误及其行号(参见[运行 Lifeguard](#running-lifeguard))。这让你可以像使用 linter 一样使用 Lifeguard:在 CI 或本地运行它,审查被标记的代码行,并修复它们。通过这种方式,Lifeguard 可作为安全启用懒加载的指南。 ### 驱动懒加载器 JSON 输出旨在驱动懒加载器的过滤函数。在 Python 3.15 中,[`importlib.util.lazy_import`](https://peps.python.org/pep-0810/) 接受一个过滤回调,用于控制哪些导入被推迟,哪些被立即加载。Lifeguard 的输出提供了构建此过滤器所需的数据 —— 使用 `LAZY_ELIGIBLE` 来识别安全模块及其约束条件,并使用 `LOAD_IMPORTS_EAGERLY` 来识别需要预先解析所有导入的模块。 我们计划在 Python 3.15 发布前提供轻松导入 Lifeguard 输出的工具。这项工作正在进行中。 ## 实现 Lifeguard 使用 Rust 实现的。我们利用 [ruff](https://github.com/astral-sh/ruff) 进行 AST 遍历,并复用了 [pyrefly](https://github.com/facebook/pyrefly) 的几个 crate。我们还扩展了 `.pyi` 存根文件,以标注第三方库中已知的副作用 —— 例如,标记依赖项中特定的模块级函数调用具有可观察的行为。这些存根存储在 `resources/` 文件夹中。有关效果标注如何与标准类型存根协同工作的详细信息,请参见 [resources/stubs/stubs.md](resources/stubs/stubs.md)。 ## 许可证 通过为 Lifeguard 做出贡献,你同意你的贡献将根据本源代码树根目录下的 LICENSE 文件进行许可。
标签:Lazy Imports, Python, SOC Prime, 云安全监控, 可视化界面, 开发工具, 性能优化, 无后门, 检测绕过, 自动化payload嵌入, 逆向工具, 静态分析