当前位置: 首页 > news >正文

Cargo deny 与依赖治理:阻止有漏洞的 crate 进入你的项目

Cargo deny 与依赖治理:阻止有漏洞的 crate 进入你的项目

更可怕的是,我问了自己三个问题:这些 crate 有已知漏洞吗?它们的开源许可证我能商用吗?有没有重复编译不同版本的同一个库(比如syn 1.xsyn 2.x同时存在)?——我一个都答不上来。

今天这篇文章,就是我用cargo-deny来解决这三个问题的一手记录。

一、cargo-deny 是什么,为什么需要它

cargo-deny是一个 Cargo 子命令,专门做依赖审计(Dependency Audit)。它检查三件事:

  1. 安全漏洞(Advisories):依赖的 crate 是否在 RustSec 数据库中登记了已知漏洞?
  2. 许可证合规(Licenses):依赖的许可证是否与你的项目兼容?
  3. 依赖源头(Sources):依赖是否来自受信任的源(比如 crates.io)?

还有一个很实用的功能:Ban 列表——你可以禁止特定 crate(或特定版本)进入项目。

# 安装 cargo-deny(只需要做一次) cargo install cargo-deny # 在项目根目录生成默认配置文件 cargo deny init # 运行完整的审计检查 cargo deny check

第一次跑完cargo deny check后的输出大概是这样:

error[A001]: net2 0.2.39 has been yanked! ┌─ /home/user/project/Cargo.lock:42:1 │ 42 │ net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index │ ------------------------------------------------------------- yanked crate detected │ = net2 v0.2.39 └── mio v0.6.23 └── tokio-reactor v0.1.12 └── ... error[L001]: failed to get license requirements ┌─ ring 0.16.20 │ = ring v0.16.20 ├── LICENSE: OpenSSL └── warning[B004]: found 2 duplicate entries for crate 'syn' ┌─ /home/user/project/Cargo.lock:85:1 │ 85 │ syn 1.0.109 registry+https://github.com/rust-lang/crates.io-index │ ------------------------------------------------------------- syn v1.0.109 │ 87 │ syn 2.0.87 registry+https://github.com/rust-lang/crates.io-index │ --------------------------------------------------------- syn v2.0.87

初次看到这个输出时,我心里只有一个想法:幸好跑了。然后开始逐项修。

二、配置 deny.toml:定制你的依赖策略

cargo deny init生成的配置文件是deny.toml,这是整个依赖治理的核心。下面是我在 AI CLI 项目中实际使用的配置,逐段解读:

# ===== deny.toml ===== # 这是我 AI CLI 项目的依赖审计配置 # ---------- 1. 安全漏洞 ---------- [advisories] # 忽略社区尚未处理的漏洞(谨慎使用!) # 只有确认过不影响自己项目才能加进来 ignore = [ # 例子:RUSTSEC-2020-0071(time crate 的段错误问题) # 我确认了我的项目不使用 time crate 的受影响 API # "RUSTSEC-2020-0071", ] # 漏洞数据库的 URL(默认是 GitHub 上的 RustSec 仓库) db-url = "https://github.com/rustsec/advisory-db" # 拉取间隔:多久检查一次是否有新的漏洞报告 db-path = "~/.cargo/advisory-db" # 如果 CI 中因为网络问题拉不到数据库,宽限期允许跳过 # 生产构建时不建议跳过(设置成 [] 就是不留情面) yanked = "deny" unmaintained = "warn" # 无人维护的 crate,给警告 unsound = "deny" # API 不安全但未修复,拒绝 notice = "warn" # 一般性公告,提醒但允许 # ---------- 2. 许可证合规 ---------- [licenses] # 我的 AI CLI 是 MIT 许可证,只允许以下兼容的许可证 allow = [ "MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "BSD-3-Clause", "Unicode-DFS-2016", "ISC", "MPL-2.0", # Mozilla 公共许可证 "BSL-1.0", # Boost 软件许可证 "CC0-1.0", # 公共领域 ] # 如果有依赖的许可证不兼容,CI 直接挂掉 # 但允许一些"例外"(手动确认过合规的) exceptions = [ # 某些 crate 许可证文件名称不规范,手动指定 # { name = "ring", allow = ["OpenSSL"] }, ] # 未知许可证的处理(有些 crate 没声明许可证) unknown = "deny" # 对商业项目这是必须的 copyleft = "deny" # GPL/AGPL 等传染性许可证不能用 # ---------- 3. 禁止列表 ---------- [bans] # 禁止引入某些 crate deny = [ # 阻止 unicode-* 系列老 crate 的意外引入 # 因为 Rust 标准库已经内置了 unicode 支持 { name = "openssl-sys", wrappers = [] }, # 我只用 rustls { name = "native-tls", wrappers = [] }, # 同上,强制 rustls ] # 允许重复依赖(不触发 warning 的例外) # 有时候同一个库的多个版本是被迫的(间接依赖冲突) skip = [ { name = "windows-sys", version = "0.45" }, { name = "windows-sys", version = "0.48" }, { name = "windows-sys", version = "0.52" }, # ↑ Windows API 的版本碎片化是 ecosystem 的问题 # 我们作为应用层只能接受,但不加 skip 会一直报警 ] # 精简依赖树:某些 crate 有更好更小的替代品 skip-tree = [ # 如果项目中已经没有使用 time crate,但依然出现在依赖树里 # 这通常是某个间接依赖的残留,需要排查 ] # ---------- 4. 依赖源限制 ---------- [sources] # 只允许来自官方 crates.io 的依赖 # 如果你的项目还会从私有 registry 拉,需要在这里配置 unknown-registry = "deny" unknown-git = "deny" # 不允许直接依赖 Git 仓库 # 如果需要内网私有 registry,在这里配置 # 但我的 AI CLI 只用公共 crates allow-registry = ["https://github.com/rust-lang/crates.io-index"]

配置完跑一遍之后,所有不合规的 crafe 都会被打回——在 CI 里加了这步之后,我再也没遇到过"不小心引了个 GPL 的库"的尴尬情况。

flowchart TD A["推送代码 / 合并 PR"] --> B["CI: cargo deny check"] B --> C{"检查1: 安全漏洞"} C -->|通过| D{"检查2: 许可证兼容"} C -->|失败| C1["CI 失败<br/>→ 通知开发者修复"] D -->|通过| E{"检查3: Ban 列表"} D -->|失败| D1["CI 失败<br/>→ 替换为兼容许可证的替代库"] E -->|通过| F{"检查4: 依赖源"} E -->|失败| E1["CI 失败<br/>→ 去除被禁用的 crate"] F -->|通过| G["✅ 构建继续"] F -->|失败| F1["CI 失败<br/>→ 确认依赖来源"] style C1 fill:#ffcdd2 style D1 fill:#ffcdd2 style E1 fill:#ffcdd2 style F1 fill:#ffcdd2 style G fill:#c8e6c9

三、修复实战:常见问题怎么解

跑完cargo deny check之后,你一定会遇到一堆报错。下面是我踩过的几个典型坑及修复方法:

# 1. 重复依赖(Duplicate crate) # 问题:syn v1.0 和 syn v2.0 同时出现 # 原因:某个间接依赖还在用 syn v1 # 解决方法: # ① 运行 cargo tree -i syn 查看是谁引用了旧版 # ② 如果那个 crate 有更新版本支持 syn v2,升级它 # ③ 如果是社区的废弃 crate,考虑替换或 fork # 可以在 [bans] 中配置 [bans] multiple-versions = "deny" highlight = "all" # 在输出中高亮显示所有重复的 crate # 2. yanked crate # 问题:某个版本被作者或社区撤回了 # 解决:直接升级或降级,没有商量余地 # 配置中已设 yanked = "deny",CI 会自动拦住 # 3. 许可证不兼容 # 问题:某个传递依赖用了 GPL-3.0 # 解决: # ① cargo tree -i <crate-name> 看是谁带进来的 # ② 找替代品:功能相同但许可证更宽松的 crate # ③ 如果是必需的且确认合规:在 licenses.exceptions 中显式声明

来看一个实际修复例子。我的项目引用了reqwest(HTTP 客户端),它默认依赖native-tls(基于 OpenSSL):

// 修复前:Cargo.toml // reqwest 默认带 native-tls,引入了一堆 OpenSSL 依赖 // [dependencies] // reqwest = { version = "0.12", features = ["json"] } // ↓↓↓ 换成 rustls ↓↓↓
# 修复后:Cargo.toml # 把 native-tls 换成纯 Rust 实现的 rustls [dependencies] reqwest = { version = "0.12", default-features = false, features = [ "json", "rustls-tls", # ← 用 rustls 代替 native-tls "http2", ]} # 依赖树对比(运行 cargo tree 可以看到): # 修复前:reqwest → hyper-tls → native-tls → openssl → openssl-sys # 修复后:reqwest → hyper-rustls → rustls → ring(纯 Rust)

不仅解决了 OpenSSL 许可证问题,还减少了编译时间(openssl-sys需要 C 编译器),二进制体积也小了。

四、把 cargo-deny 嵌入 CI/CD

依赖审计最大的价值在于自动化。手动跑一次只能解决当下的问题,但新人加了个依赖、或者老依赖被发现新漏洞,你不会知道。

在 GitHub Actions 中配置:

# .github/workflows/audit.yml name: 依赖审计 on: push: branches: [main] pull_request: branches: [main] # 每天定时跑一次(漏洞数据库在更新) schedule: - cron: '0 8 * * *' # 每天早上 8 点 jobs: audit: name: Security Audit + License Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: 安装 Rust 工具链 uses: dtolnay/rust-toolchain@stable - name: 安装 cargo-deny run: cargo install cargo-deny --locked - name: 运行依赖审计 run: cargo deny check advisories bans licenses sources # 四个子命令分别检查: # advisories → 安全漏洞 # bans → 禁止列表 + 重复依赖 # licenses → 许可证合规 # sources → 依赖源
gantt title 依赖治理的持续监控节奏 dateFormat HH:mm axisFormat %H:%M section 每次 PR cargo deny check :active, pr1, 00:00, 2min section 每日定时 拉取 RustSec 数据库 :daily1, 08:00, 30s 全量审计扫描 :daily2, after daily1, 2min 发送告警(如有漏洞):daily3, after daily2, 10s section 每月 依赖更新评审 :monthly1, 00:00, 30min 淘汰过时依赖 :monthly2, after monthly1, 20min

cargo-deny 的定时任务还需要注意:schedule总是跑在默认分支,如果 main 分支和开发分支的deny.toml不一致,定时审计报的漏洞可能跟线上实际环境对不上。建议在 CI 脚本里显式 checkout 到稳定分支。

还有一个现实问题:cargo-deny报的 RUSTSEC 漏洞有些是"理论上的",生产环境不一定能被利用。对于无法立即升级的依赖,可以先用deny.tomlskip字段放行,但必须加注释写明原因和计划修复时间。裸的 skip 没有注释,一星期后没人记得为什么绕过。

五、总结

这篇文章我从"287 个依赖我一个都不知道"的恐慌出发,介绍了cargo-deny的配置和使用方法,以及如何把它嵌入 CI/CD 流程中。

Rust 生态的 crate 数量庞大、质量参差不齐,依赖治理不是一次性的工作,而是一个持续的习惯。我的建议是:

  1. 项目一开始就配置 cargo-deny:不要等项目 500 个依赖了才想起来审计,那时候改一个传递依赖可能需要重构半个项目
  2. CI 里跑,不是本地跑:手动跑没有持续性,下一周可能就忘
  3. 不要跳过 yanked = "deny":看似方便,实则埋雷
  4. 依赖少就是最好的安全策略:定期运行cargo tree看看哪些依赖可以去掉

作为自学者,依赖治理这个词听起来很"高级工程师",但实操起来其实就是 "cargo deny check + 看报错 + 改 Cargo.toml" 三步走。不要被术语吓到,动手跑一次就明白了。

如果你的项目从来没跑过依赖审计,今天就可以试试cargo deny init && cargo deny check,惊喜(或者惊吓)在等着你。我们下篇见!

http://www.jsqmd.com/news/1196192/

相关文章:

  • 2026 最新免费 edu 教育邮箱注册教程|Liberty University 亲测可用(附养号防封方法)
  • 【JAVA 进阶】Spring Boot自动配置详解:从原理到实战
  • 2026菏泽楼顶漏水、卫生间漏水、外墙、地下室、阳台渗漏?3家口碑企业推荐(7月) - 防水企业百科
  • C++构建高并发校医院服务平台:从架构设计到性能优化实战
  • AtCoder新手入门:从注册到首场ABC比赛的完整实战指南
  • UVA253 骰子涂色 Cube painting
  • 万字详解Java枚举:从基础语法到底层原理,吃透实战用法
  • C语言轻量级RSA库集成指南:三步实现嵌入式数据加密
  • 示波器高级显示模式:从余晖到冻结的实战解析
  • Istio 到 Cilium Service Mesh 的迁移评估:eBPF 能否替代 Sidecar
  • 万物的骨架:深入理解 Mesh(网格)
  • 苏州家里房子漏水找谁修更合适?不同漏水场景的选型建议盘点 - 徽顺虹
  • 北京急出邮票纪念币不用等!全域快速上门回收,当场结清全款 - 深鉴新闻
  • 【VMware教程】RHEL 9虚拟机搭建网盘
  • TDA2E-17时钟系统设计:从DPLL配置到VIP时序的嵌入式实战指南
  • 在断网的银河麒麟系统上装个软件,比找对象还难?用这个方法像点外卖一样简单!
  • 在深圳遇到这8种情况,不用犹豫,直接找无忧白蚁 - GrowthUME
  • 深入解析TMS320C6654:TeraNet互连与CorePac内存管理实战指南
  • 银河麒麟高级服务器操作系统V10SP2(ARM)下Mellanox网卡驱动编译与内核适配实战
  • 2026GEO优化公司有哪些,看懂GEO服务的价格和效果再选
  • ARM ETM 指令追踪实战:利用 perf + OpenCSD 解码控制流做嵌入式代码覆盖率分析
  • 机器学习特征预处理之分类缺失填充
  • 7.15 答辩稿
  • 新手做抖音小店无货源,商品卡自然流怎么选品?工具直接套用 - 电商分享
  • 如何用 5 种终极方法将 Redmi 手机恢复出厂设置
  • 2026福州外墙渗漏、卫生间漏水、楼顶、地下室、阳台+阳光房渗漏别踩坑!3家正规服务商(7月) - 防水企业百科
  • 编程启蒙|Scratch 转 Python 系列第8天:节奏敲击机游戏实战(AI节奏谱生成实战)
  • 边缘推理多实例并发服务架构设计:模型共享内存池与请求级 Arena 隔离方案的工程实现
  • CSS弹性盒子布局(Flexbox)详解
  • 双二阶IIR滤波器——定点数实现与结构选择考量