Correct Code, Vulnerable Dependencies: A Large Scale Measurement Study of LLM-Specified Library Versions
Correct Code, Vulnerable Dependencies: A Large Scale Measurement Study of LLM-Specified Library Versions
原文链接:https://arxiv.org/abs/2605.06279 作者:Chengjie Wang、Jingzheng Wu、Xiang Ling、Tianyue Luo、Chen Zhao 机构:Institute of Software, Chinese Academy of Sciences;University of Chinese Academy of Sciences;Key Laboratory of System Software (CAS) 发布日期:2026-05-07
速查卡
| 项目 | 内容 |
|---|---|
| 一句话总结 | LLM 不只是会 hallucinate 包名,它更隐蔽的风险是:会为真实存在的依赖钉上真实存在、但带 CVE 或根本不兼容的版本号。 |
| 大白话版 | 模型帮你写的 Python 代码也许逻辑没问题,但它顺手写上的 requests==...、django==...、cryptography==... 版本,可能一装就炸,或者装得上却自带已知漏洞。 |
| 核心数字 | • 基于 1000 个 Stack Overflow 任务构建 PinTrace • 10 个 LLM 全测 • 明示版本时 36.70%–55.70% 的任务至少带 1 个已知 CVE • 62.75%–74.51% 的相关漏洞是 Critical/High • 静态兼容率 19.70%–63.20%,动态通过率 6.49%–48.62% |
| 评级 | A — 这不是边角安全问题,而是 LLM coding workflow 里一个此前被系统性忽视的主风险面 |
| 代码 | 数据集与代码已开源:https://github.com/dw763j/PinTrace |
| 关键词 | dependency versioning, CVE, supply chain security, PinTrace, BigCodeBench, Python, LLM coding assistants |
核心 Insight
这篇论文最厉害的地方,在于它把一个之前经常被忽略的问题单独拎了出来:
过去大家谈 LLM 写代码的安全问题,主要盯两件事:
- 代码逻辑本身会不会有 CWE 式漏洞;
- 模型会不会 hallucinate 一个根本不存在的包名。
这篇论文说:还有第三类,而且在真实开发里可能更阴。
那就是模型给你推荐的包名是对的,代码看起来也对,但它偷偷选择了一个带已知 CVE、或者和代码 API 不兼容的历史版本。
这个问题为什么严重?因为它很容易通过第一层人工 review:
- import 语句看起来很正常;
- 版本号也是“真的”;
- 甚至有时候还能 install 成功。
但风险在于:
- 你把旧漏洞一起带进来了;
- 或者代码和版本 API 不匹配,部署时才炸;
- 更糟的是,不同模型还会收敛到同一批“高频但脆弱”的版本上。
为什么这个想法 work?
因为依赖版本选择本质上不是一个“语言建模小细节”,而是软件供应链决策。
人类开发者在选版本时,会参考:
- changelog
- 安全通告
- 兼容性说明
- Python / OS / transitive deps 环境
LLM 则是按训练语料中的显著性和共现关系做推断。一个版本在网上出现得越多、教程越多、Stack Overflow 答案越多,它就越可能被模型选中;而这类“高曝光老版本”恰恰最有机会积累 CVE、踩中新环境不兼容坑。
所以这篇论文要证明的核心不是“某个模型粗心”,而是: 模型在做版本决策时,遵循的是语言统计偏好,而不是安全与兼容性目标。
方法详解
整体架构
论文构建了一条非常完整的版本风险评测流水线:
真实 Stack Overflow 任务
→ 让 10 个 LLM 生成带依赖的 Python 代码
→ 抽取第三方库与版本标注
→ 检查版本是否真实存在
→ 对照 OSV 索引看是否带已知 CVE
→ 在隔离环境中安装并做静态兼容检查
→ 用 BigCodeBench 测动态执行
→ 再做 root cause 与 mitigation probe
重点是作者把“安全性”和“兼容性”分开测:
- 安全:版本有没有已知漏洞;
- 兼容:这个版本和生成代码能不能一起工作。
数据集:PinTrace
组件 1:任务来源
做什么: 从真实开发问题里构造版本评测任务。
怎么做:
- 从 Stack Overflow 抽题
- 只保留有 accepted answer 的问题
- accepted answer 必须含 Python 代码块
- 代码块能 parse 成 AST
- 至少 import 一个第三方库
- 时间窗口限定在 2020-01-01 到 2026-01-01
结果: 形成 1000 个任务的 PinTrace 数据集。
组件 2:为什么只测 Python
作者只测 Python,不是偷懒,而是为了实验闭环:
- Python 是 LLM-assisted coding 的高频语言
- PyPI 版本历史完整
- OSV / PyPI 元数据足够成熟,适合做大规模版本解析与漏洞索引
- Python 生态 release 节奏快,最容易暴露版本级兼容问题
组件 3:平衡采样
作者没有直接随机抽 Stack Overflow,因为那会被 pandas、numpy 之类高频库淹没。于是他们做了 TPL-balanced sampling,保证 267 个不同第三方库都有足够覆盖。
数据集统计
| 指标 | 数值 |
|---|---|
| 任务数 | 1000 |
| 覆盖第三方库数 | 267 |
| 问题 token 长度均值 / 中位数 | 430.6 / 286.5 |
| accepted answer 代码 token 均值 / 中位数 | 202.0 / 123.5 |
| 每任务平均 TPL import 数 | 1.6 |
| 至少 2 个 TPL import 的任务占比 | 46.4% |
| 至少 3 个 TPL import 的任务占比 | 13.1% |
这很关键,因为版本冲突和多依赖交互,必须在有多库组合的任务里才会充分暴露。
被测模型
论文评估 10 个模型,包含闭源和开源:
- GPT-5.4
- Claude-Sonnet-4.6
- Gemini-3.1-Pro
- DeepSeek-V3.2
- Kimi-K2.5
- Qwen3.5-397B
- Qwen3-235B
- Qwen3-30B
- MiniMax-M2.5
- Llama-4-Scout
这使结论不容易被解释成“某一家模型训练差”。
两种 prompting mode
这是论文设计里非常妙的一点。
作者区分两种场景:
-
Inline mode
- 模型在代码里直接对 import 或依赖附上版本信息
- 这更像“模型主动做版本决策”
-
Explicit mode
- 让模型直接创建 manifest / requirements 一类文件
- 若没写版本,安装时会回退到最新 release
这个区分非常重要,因为它揭示:
- inline 更容易显式带来漏洞版本;
- explicit 则可能因为“没钉版本”把兼容性交给环境,形成另一类风险。
漏洞索引构建
作者基于 OSV 构建 (library, version) 到 CVE / CVSS 的映射:
- 只保留 PyPI 生态记录
- 解析 explicit version lists 与 range specs
- 结合完整 PyPI release history 扩展受影响版本
- 去重 alias 到 canonical CVE
兼容性检查链路
兼容性分两层:
静态兼容
- 能不能装上(installation verification)
- 能不能通过
tystatic type checking
动态兼容
- 在 BigCodeBench 上跑测试
- 看任务级是否 pass / fail / error
这套设计很好,因为很多版本问题根本跑不到业务逻辑层:安装就先炸了。
与现有方法的关键区别
| 维度 | 之前常见做法 | 本文方法 | 为什么更好 |
|---|---|---|---|
| 安全焦点 | 代码逻辑漏洞 / hallucinated packages | 真实依赖的真实版本是否安全 | 抓住更隐蔽也更常见的风险面 |
| 数据来源 | 小样本或合成任务 | 1000 个真实 Stack Overflow 任务 | 更接近真实开发场景 |
| 依赖分析 | 只看包名是否存在 | 看版本是否存在、是否带 CVE、是否兼容 | 闭环完整 |
| 验证方式 | 静态分析常见 | OSV + 安装 + 类型检查 + BigCodeBench 动测 | 能区分不同失败来源 |
实验结果
主实验 1:模型到底有多爱“指定版本”
在是否主动钉版本这件事上,prompting mode 的影响比模型能力本身还大。
论文报告:
- 在一种提示条件下,模型显式指定版本的比例可高达 95.18%
- 在另一种 manifest 场景下,有些模型只在 6.45%–59.19% 的情况下写出版本
这说明“模型是否在做版本决策”本身就受提示设计强烈影响。
主实验 2:漏洞暴露率高得离谱
| 指标 | 结果 |
|---|---|
| 任务层面至少 1 个已知 CVE 的占比 | 36.70%–55.70% |
| 脆弱版本中 Critical / High 的占比 | 62.75%–74.51% |
| 相关 CVE 在模型知识截断前已公开的比例 | 72.27%–91.37% |
| 跨全部模型聚合的 pre-cutoff CVE-model 对 | 81.8% |
解读:
- 这不是“模型不知道漏洞后来才被披露”。大多数漏洞在训练前就已经公开。
- 问题不在于模型完全无知,而在于模型无法在推理时把“版本字符串”稳定映射到“漏洞数据库事实”。
- 所以一句“请避免有 CVE 的版本”基本没用。
主实验 3:静态兼容率并不高
| 模式 / 指标 | 范围 |
|---|---|
| 静态兼容率(所有模型) | 19.70%–63.20% |
| 动态通过率(所有模型) | 6.49%–48.62% |
细看 BigCodeBench,问题更刺眼:
- inline mode 下,9/10 模型动态通过率低于 20%
- error 列非常高,说明大量任务根本没跑到测试逻辑,先死在安装 / 依赖解析阶段
代表性结果表:版本暴露与兼容性
| 模型 | 任务漏洞暴露率 τU(基线) | 静态兼容率 τC(基线) | BigCodeBench 动态通过率(基线) |
|---|---|---|---|
| GPT-5.4 | 46.20% | 77.35% | 48.62% |
| Claude-Sonnet-4.6 | 50.50% | 16.16% | 7.32% |
| Gemini-3.1-Pro | 50.70% | 35.91% | 18.23% |
| DeepSeek-V3.2 | 54.00% | 31.63% | 15.61% |
| Kimi-K2.5 | 55.70% | 32.60% | 13.54% |
| Qwen3.5-397B | 50.40% | 20.03% | 9.81% |
| Qwen3-235B | 51.70% | 28.73% | 6.91% |
| Qwen3-30B | 36.70% | 41.57% | 16.16% |
| MiniMax-M2.5 | 52.10% | 15.88% | 6.49% |
| Llama-4-Scout | 51.70% | 23.34% | 8.15% |
解读:
- GPT-5.4 是明显的 outlier,兼容性最好,但漏洞暴露仍不低。
- 这说明“代码能力更强”并不自动解决“版本风险”。
- 大部分模型的主问题不是代码语法,而是依赖解析根本走不通。
主实验 4:模型会跨家族收敛到同一批脆弱版本
这是论文最震撼的发现之一。
作者统计到:
- 共有 6378 次 vulnerable version assignment
- 分布在 1289 个唯一的脆弱
(library, version)对上 - 其中 top-10 就占了 26.34%
最常见的高频脆弱版本包括:
django==6.0.1requests==2.31.0flask==3.1.2flask==2.3.3djangorestframework==3.14.0
其中 django==6.0.1 在全部 10 个模型里累计出现 400 次。
这说明风险不是某家模型的偶发 bug,而是整个训练分布在把模型往同一批“高曝光旧版本”上推。
静态兼容失败的主因:安装阶段就死
论文把不兼容任务的首要诊断标签做了统计。最主要的失败类别是:
- dependency installation error
- unresolved attribute reference
- unresolved import
- invalid argument type
而安装失败里,最主要的两个子因是:
- dependency resolution conflict
missing_distutils
后者很有意思:很多老版本包依赖的构建流程仍假设 Python 自带 distutils,但 Python 3.12 把它从标准库移除了。于是模型偏爱的“旧但常见版本”,在现代环境里反而更容易装不上。
环境敏感性:3.10 和 3.12 是一道分水岭
论文又做了 Python 3.8 / 3.10 / 3.12 / 3.14 的鲁棒性实验。
结论很清楚:
- inline mode 在 Python 3.10 附近兼容性最好
- 到 3.12 后明显塌
- explicit mode 则往往随 Python 版本更新而改善
以 DeepSeek-V3.2 为例:
- inline 静态兼容率从 Python 3.10 的 90.77%,掉到 Python 3.12 的 34.19%
- BigCodeBench 动态通过率从 38.25% 掉到 16.73%
这进一步证明:问题核心在模型钉的版本,不在代码逻辑本身。
邻近版本替换实验:能救回来多少
论文做了一个非常有说服力的因果实验:
- 固定生成代码不变
- 只在每个失败依赖周围搜索相邻版本(最多 ±3 个 release index 邻居)
- 看是否能恢复安装与静态兼容
结果:
- 17.19%–55.00% 的 qualifying tasks 可以仅靠换邻近版本恢复安装
- 7.81%–46.04% 可以进一步恢复到静态兼容
- 跨所有模型,被替换后新引入 CVE 的恢复任务总共只有 5 个
这几乎是钉死结论了: 很多失败真不是代码写错,而是版本选错。
Mitigation probe:一句“注意安全”没啥用,外部 version anchor 才有用
作者比较了四种条件:
- Baseline
abl-instruct:加自然语言安全提示,要求避开有 CVE 的版本abl-version:为每个库注入预计算的较安全版本锚点abl-rag:在 version anchor 基础上,再检索 top-20 API signatures 注入 prompt
结果非常直接:
-
安全提示几乎没用
- 对大多数模型,漏洞暴露率只改善 0–2.2 个百分点。
-
version anchoring 有显著效果
- 任务漏洞暴露率 τU 压到 10.90%–27.60%
- 静态兼容率提升到 80.92%–93.64%
- 动态通过率提升到 36.31%–54.56%
-
再加 RAG 收益很有限
- τC 只比
abl-version多 0.10–2.05 个点 - 动态通过率最多多 1.03 个点
- 有时还会倒退
- τC 只比
这说明版本风险的最优解不是“让模型自己想明白”,而是给它外部约束和 grounding。
复现评估
| 维度 | 评分(1-5) | 详细说明 |
|---|---|---|
| 数据可得性 | ⭐⭐⭐⭐⭐ | PinTrace 已公开,Stack Overflow / PyPI / OSV 都可复用。 |
| 代码可得性 | ⭐⭐⭐⭐ | GitHub 已给出,但完整跑通仍要处理环境与 API 调度。 |
| 算力需求 | ⭐⭐⭐ | 10 模型 × 1000 任务不算便宜,但比大规模训练论文更可复现实验。 |
| 工程复杂度 | ⭐⭐⭐⭐ | 需要版本解析、OSV 索引、隔离环境安装、BigCodeBench 动测。 |
| 预期收益 | ⭐⭐⭐⭐⭐ | 对 IDE、Copilot 类产品、企业 DevSecOps、内部 coding platform 都很有现实价值。 |
复现建议: 最实用的复现实验不是全量抄论文,而是:
- 抽你们团队最常见的 100 个依赖型 coding task;
- 统计模型会不会显式钉版本;
- 把版本过一遍 OSV / pip audit / internal allowlist;
- 再测 install success 与 smoke tests。
批判性分析
局限性(论文承认的 + 我们额外看到的)
-
只覆盖 Python / PyPI
- npm、Maven、Cargo 等生态未纳入,跨语言结论还不能直接外推。
-
依赖风险还没完全覆盖 transitive chain 的深层行为
- 论文已经碰到 dependency conflicts,但对多层传递依赖的长期演化影响仍未完全展开。
-
真实企业项目的私有镜像、锁文件策略、内部仓库策略没有被模拟
- 因此它更像“公共互联网开发场景”的强结论。
-
baseline 指标和 prompting mode 强耦合
- 某些结果会受到“你怎么让模型输出版本”这个提示设计影响,这既是现实,也是解释时要小心的地方。
改进方向
-
跨生态扩展
- 下一步最该做 npm / Maven / Rust crate 版 PinTrace。
-
引入锁文件与企业策略
- 看
requirements.txt、poetry.lock、内部镜像 allowlist 对风险的实际缓释效果。
- 看
-
把安全与修复联动起来
- 不只测“错没错”,还测模型能否在给定 vulnerability context 后自动修到安全版本。
独立观察
-
这篇论文实际上给“AI 代码生成安全”换了坐标系。
- 以后不能只问“代码对不对”,还得问“依赖是不是被模型顺手带歪了”。
-
它和最近关于 constraint decay、maintainability 的工作连起来看,会得到一个更完整的判断:
- coding agent 的问题越来越不是“生成不出来”,而是“生成得太像能用,但供应链和工程纪律在暗处漏水”。
-
对产品方最重要的建议不是换个更聪明模型,而是把外部 policy / OSV / allowlist / version anchoring 接进生成链路。
对领域的影响
短期内,这篇论文很可能推动:
- IDE / agent 平台把 dependency guardrail 变成标配
- 生成式代码评测从“逻辑漏洞”扩展到“版本漏洞”
- 企业把 SCA(software composition analysis)前移到 LLM 生成时刻
中期内,更成熟的 coding agent 体系应该长成这样:
- 模型负责生成意图与代码骨架
- 外部 resolver / policy engine 负责版本落地
- 安全与兼容性不再交给模型单独拍脑袋
一句话,这篇论文不是在说“模型写代码不行”,而是在说: 如果你让模型独自决定依赖版本,它会很稳定地把你带进一个又脆又不兼容的统计陷阱里。