Cargo 审计与供应链安全:cargo-deny 和 cargo-audit 防什么

📅 2026/7/10 14:36:12
Cargo 审计与供应链安全:cargo-deny 和 cargo-audit 防什么
Cargo 审计与供应链安全cargo-deny 和 cargo-audit 防什么一、Rust 的依赖让人又爱又怕Rust 的 Cargo 包管理系统是这门语言最让人舒适的部分之一。几行Cargo.toml配置就能引入功能强大的 crate从 HTTP 客户端到 JSON 解析器到异步运行时应有尽有。但方便的背后也埋着两个问题。第一个问题是依赖膨胀。你可能cargo add了三个看上去不相关的 crate但它们各自又依赖了上百个子 crate。这些间接依赖的代码会在编译时被打进你的二进制文件中但你很可能一个都没审查过。第二个问题是版本漂移。你引入的依赖在不断更新这些更新可能包含安全漏洞的修复也可能带来新的问题。而当你从不知道这些更新在发生什么时你的项目实际上是在一条你从未看过的河流里游泳。flowchart LR A[你的项目br/Cargo.toml] -- B[直接依赖br/serde, tokio, reqwest...] B -- C[间接依赖br/serde_derive, mio...] C -- D[更深层依赖br/proc-macro2, unicode-ident...] B -- E[安全威胁来源] C -- E D -- E E -- F[已知漏洞br/CVE / RUSTSEC] E -- G[恶意代码注入br/供应链攻击] E -- H[许可证冲突br/GPL 传染风险] E -- I[废弃依赖br/无人维护 安全隐患] style A fill:#336,stroke:#48a,color:#fff style F fill:#933,stroke:#c44,color:#fff style G fill:#933,stroke:#c44,color:#fff style H fill:#963,stroke:#c84,color:#fff style I fill:#963,stroke:#c84,color:#fff这张图说明了一个关键问题安全风险不只存在于直接依赖中任何一层间接依赖都可能引入漏洞。而人类不可能手动审查每个间接依赖这正是自动化审计工具的价值所在。二、cargo-audit —— 防已知漏洞的守门人cargo-audit是 RustSec 组织维护的命令行工具它会扫描你的Cargo.lock文件比对 RustSec 安全公告数据库rustsec.org检查你的依赖树中是否存在已知的 CVE 漏洞或已报告的安全问题。使用非常简单一个命令搞定。// 这不是要编译的 Rust 代码而是 cargo-audit 的工作原理示意 // cargo-audit 在检查时 // 1. 读取 Cargo.lock 解析完整的依赖树 // 2. 从本地或远程拉取 RUSTSEC 安全公告数据库 // 3. 逐一比对每个依赖的 (名称, 版本) 是否命中任何公告 // 4. 输出命中结果并返回对应退出码0 安全, 非 0 有问题 // Cargo.toml 示例 —— 这是你的项目可能包含的依赖 // [dependencies] // openssl 0.10.45 # 假设这个版本存在已知漏洞 // h2 0.3.18 # 某些旧版本有 HTTP/2 拒绝服务漏洞 // // cargo-audit 检查后会输出类似 // Crate: h2 // Version: 0.3.18 // Title: Resource exhaustion vulnerability in h2 // Date: 2024-01-17 // ID: RUSTSEC-2024-0003 // URL: https://rustsec.org/advisories/RUSTSEC-2024-0003 // Solution: Upgrade to 0.3.24 OR 0.4.2使用cargo-audit不需要写代码但需要嵌入到你的 CI/CD 流程中。我建议在 CI 流水线的test阶段之前执行cargo audit如果发现漏洞就中断流水线防止有漏洞的代码被部署出去。cargo-audit的局限在于它只能识别已知的漏洞。如果你的依赖有零日漏洞或者还没有被 RustSec 数据库收录cargo-audit是无能为力的。这也是为什么需要和其他工具配合使用。# 安装 cargo-audit cargo install cargo-audit # 基础用法检查当前项目依赖 cargo audit # 在 CI 中使用非 0 退出码会中断流水线 cargo audit --deny warnings # 忽略特定漏洞仅在不影响你的用法时使用要谨慎 cargo audit --ignore RUSTSEC-2024-XXXX三、cargo-deny —— 不止于安全更管许可证和重复依赖cargo-deny的功能比cargo-audit更广。除了安全审计集成了 RustSec 数据库它还提供了三块核心能力许可证合规检查、依赖来源白名单控制和重复依赖检测。重复依赖在某些场景下会成为安全风险的一个放大因素。如果你声明了uuid 0.8而某个间接依赖又用了uuid 1.0你的二进制文件里会同时包含两个版本的 uuid crate。如果其中一个版本存在漏洞但你只修复了另一个攻击者仍然可以利用。// deny.toml 配置示例 —— cargo-deny 的配置文件 // 放在项目根目录cargo-deny 运行时会自动读取 // 安全公告检查 // 这部分和 cargo-audit 重叠但 cargo-deny 把它集成在同一个配置里 [advisories] vulnerability deny // 发现漏洞直接拒绝中断构建 unmaintained warn // 无人维护的 crate 发出警告 yanked deny // 被 yank 的版本直接拒绝 notice warn // 安全通知发出警告 // 临时忽略某个漏洞需要记录原因和截止日期 ignore [ { id RUSTSEC-2024-0001, reason 仅用于开发环境不影响生产 }, ] // 许可证合规 [licenses] unlicensed deny // 没有许可证的 crate 直接拒绝 allow [ MIT, Apache-2.0, BSD-3-Clause, ISC, ] // 明确拒绝某些具有传染性的许可证 deny [ GPL-3.0, // GPL 有传染性你的项目也会被迫 GPL ] // 某些特定 crate 例外已知许可证但分类有争议 [[licenses.exceptions]] name ring allow [ISC, MIT, OpenSSL] version * // 重复依赖检测 [bans] multiple-versions deny // 同一 crate 的多个版本将触发生成失败 wildcards deny // 不允许使用 * 通配符版本号 highlight all // 指定哪些 crate 可以被跳过重复检测 skip [ { name winapi, version * }, // Windows API 绑定版本差异合理 { name windows-sys, version * }, ] // 依赖来源白名单 [sources] unknown-registry deny // 拒绝来自未知注册源的依赖 unknown-git deny // 拒绝来自未知 Git 仓库的依赖 // 只允许从 crates.io 拉取依赖 allow-registry [https://github.com/rust-lang/crates.io-index]上面这个deny.toml是cargo-deny的核心配置文件。对于个人项目我通常把许可证这块设得宽松些毕竟我不卖软件但bans的multiple-versions我会保持为deny。重复依赖不只是浪费编译时间更是一个容易被忽视的安全敞口。cargo-deny还有一个sources模块用来限制依赖的来源。如果你在Cargo.toml里指定了一个私有 Git 仓库作为依赖而那个仓库后来被删除了或者被替换成恶意版本sources配置的unknown-git deny规则可以在一定程度上防范这种风险。四、将供应链检查嵌入开发流程光装工具还不够需要把它们嵌入到日常开发流程里才能真正发挥作用。我的做法是在三个环节里加入检查graph LR A[开发者提交代码] -- B[pre-commit hookbr/本地快速检查] A -- C[CI 流水线br/完整扫描] B -- D[git push] D -- C C -- E{是否通过} E --|通过| F[合并到主分支] E --|不通过| G[驳回 通知开发者] H[定时任务br/每周全量扫描] -- I{发现新漏洞} I --|是| J[自动创建修复 Issue] I --|否| K[记录扫描结果] style B fill:#336,stroke:#48a,color:#fff style C fill:#393,stroke:#4a4,color:#fff style H fill:#963,stroke:#c84,color:#fff style G fill:#933,stroke:#c44,color:#fff其中 CI 环节的配置是重中之重。我平时会设置成任何cargo audit或cargo deny check非零退出码都会导致 CI 失败。这种方式虽然严格但在安全性上是一个硬约束。即使代码已经上线供应链检查也不能停。我用 GitHub Actions 的schedule触发器每周运行一次全量扫描。因为漏洞公开和 crate 版本更新是持续发生的一个月前安全的依赖今天可能就不安全了。五、总结cargo-audit专注于安全漏洞扫描对接 RustSec 公告数据库cargo-deny功能更全面同时覆盖安全审计、许可证合规、重复依赖和来源管理。两者配合使用再加上 CI 集成和定期扫描可以大幅降低依赖链的安全风险。作为自学者我对安全的理解还在进步中。这篇文章分享的工具和流程都是我在实际项目中证实有效的如果你也在关心 Rust 项目的供应链安全希望这篇文章能给你一个上手的方向。如果哪些地方说得不对欢迎评论区指正。