基于 Diff 的 SCA 配合 AI 有问题——Pipfile.lock、yarn.lock 和 Cargo.lock 的真实案例

目录

软件成分分析(SCA)​ 是用于识别应用程序代码库中使用的开源和第三方组件的过程。SCA 工具通常会识别已知漏洞、许可证合规问题以及软件包的其他元数据。安全和开源合规团队是 SCA 工具的主要使用者。

DevOps 时代的 SCA 及对 Pull Request 扫描的需求

在 DevOps 时代,SCA 被集成到了 CI/CD 流程中。Pull Request(PR)会被扫描,以识别新引入的有漏洞的以及新近发现的恶意软件包。DevOps 的**"快速失败"**承诺通过在发现任何存在漏洞的软件包时阻止 PR 合并来实现。为此,SCA 工具需要识别 PR 中仅新增的软件包,而不是整个代码库。

虽然这看起来只是库存收集和漏洞数据库匹配这样一个简单的过程,但由于各种包管理器、锁文件和漏洞数据库的复杂性,SCA 工具在这些年变得越来越复杂。

AI 时代的 Diff-based SCA 扫描器

在 AI 时代,Diff-based SCA 扫描似乎成为了一种新方法。这是一种绕过处理各种包管理器、语言以及开源包如何在代码库中引入的复杂性和细微差别的捷径。只需将 git diff 或其 GitHub API 等效内容扔给 LLM,它就会施展其魔力。例如,考虑以下提示:

text
You are a software engineer reviewing a pull request in DIFF FILE FORMAT. Analyse the diff below and identify the added, updated and removed packages in the lockfile. Provide your output following the JSON schema: [ { "package_name": "string", "version": "string", "change_type": "added | updated | removed" } ] Here is the diff: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..44ec824 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +bittenso-cli==9.9.4 +qbittensor==9.9.4 +bitensor==9.9.5 +bittenso==9.9.5

Gemini 2.5 Flash 每次都会提供以下输出(10/10 概率):

json
[ { "package_name": "bittenso-cli", "version": "9.9.4", "change_type": "added" }, { "package_name": "qbittensor", "version": "9.9.4", "change_type": "added" }, { "package_name": "bitensor", "version": "9.9.5", "change_type": "added" }, { "package_name": "bittenso", "version": "9.9.5", "change_type": "added" } ]

这可能看起来是一个很好的方法,完全避免了锁文件解析的复杂性,但这种方法存在一些固有的局限性。


在这篇博客文章中,我们将探讨 diff-based SCA 扫描器如何工作、它们的局限性,以及为什么它们很容易被恶意行为者绕过。

Diff-based SCA 扫描器的工作原理

基于 git diff 的扫描器在 Pull Request(PR)中分析从 VCS 平台(如 GitHub)或基于分支变更在本地计算的 .diff 文件数据。

这些扫描器尝试从包管理器的锁文件中提取更新的软件包,如 package-lock.jsongo.modrequirements.txt 等,并使用提取的数据作为识别新引入漏洞软件包的真实来源。

这种提取可以通过解析(包括使用正则表达式和其他复杂算法)来完成,也可以利用人工智能(AI)​模型来识别锁文件中的变更。扫描器使用 +- 符号在 .diff 文件中识别添加、更新和删除的软件包。

无论使用哪种方法,基于 diff 的 SCA 扫描器都有一个固有的局限性,这是由 git diff 的工作方式决定的。

部分数据源问题

来自 VCS 平台的 diff 文件数据,甚至 git diff 命令本身,只包含变更,这意味着它只有部分数据。这是设计如此,因为 diff 的目的是显示变更,而不是整个文件。

因此,对于使用多行语法来表示软件包状态的锁文件,当软件包名称或版本数据在 .diff 文件数据中缺失时,数据很容易被遗漏和操纵。

一些使用多行语法的锁文件包括:

  • Pipfile.lock
  • poetry.lock
  • pom.xml
  • uv.lock
  • yarn.lock
  • Cargo.lock

示例:​

以下是 Pipfile.lock diff 的示例片段,显示了软件包版本从 0.4.2 更新到 0.4.5,但由于 git diff 的工作方式,未变更的行会被忽略并包装在 hunk@@ -139,7 +139,7 @@)中,我们无法获知软件包名称。

diff
diff --git a/Pipfile.lock b/Pipfile.lock index 3cfcaeee35..98a61e32bf 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -139,7 +139,7 @@ "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" ], "markers": "sys_platform == 'win32'", - "version": "==0.4.2" + "version": "==0.4.5" }, "coverage": { @@ -147,50 +147,50 @@ "toml"

因此对于这种情况,SCA 扫描器无法知道哪个软件包版本被更新,因此无法检查漏洞,这是扫描器的一个明显缺陷。

另一个示例:​

以下是 pnpm-lock.yaml diff 的示例片段,显示了软件包名称在 git hunk@@ -1115,20 +1115,20 @@ importers: 中被截断,这是高度非确定性的,因为我们不知道 git diff 将如何截断数据。

diff
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c91da270bcc6..a41f0956e3d52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1115,20 +1115,20 @@ importers: specifier: 1.0.0 - version: 1.0.0 + version: 1.0.2 '@types/babel__code-frame': - specifier: 7.0.2 - version: 7.0.2 + specifier: 7.0.6 + version: 7.0.6

来自 yarn.lock diff 文件的类似示例:​

diff
diff --git a/yarn.lock b/yarn.lock index 6809cdb40b..8dc0b8bf37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1766,6 +1766,16 @@ cli-cursor@^2.1.0: dependencies: - restore-cursor "^2.0.0" + restore-cursor "^2.0.1"

这些示例清楚地展示了基于 diff 的 SCA 扫描器的部分数据源问题,这很容易被恶意行为者利用来绕过 SCA 扫描器。

解决方案

考虑到问题的性质,特别是在 diff 文件中缺少所需上下文的情况下,解决方案需要权衡:

  1. 以处理复杂性为代价换取完整性和可靠性
  2. 以遗漏某些软件包为代价换取简单性和易用性

对于严肃的安全用例,完整性和可靠性是关键。可以通过启发式方法(如将 diff hunk 大小调大)来尝试,但这不能保证所需上下文会出现。此外,LLM 本质上是非确定性的,因此对于相同输入输出可能会不同。

对于 vet 及其 GitHub Action 集成 vet-action,我们决定做这些繁重的工作,因为 vet 已经了解不同的锁文件格式并能处理各种包管理器的细微差别。为了确保可靠性,我们遵循以下步骤:

  1. 识别 PR 中变更的锁文件
  2. 从基础分支获取完整锁文件,用 vet 解析它,并将其作为后续扫描步骤的例外
  3. 从头部分支获取完整锁文件,在存在前一步骤例外的情况下用 vet 扫描它

这种方法确保了可靠性,同时避免了解析器差异——即不同的解析器可能产生不同的结果。这也保持了 vet 锁文件解析器作为唯一真实来源。

结论

在这篇博客中,我们探讨了基于 diff 的 SCA 扫描器的细微问题,这些问题很容易被恶意行为者利用,或者软件包会被遗漏。这些是我们注意到的一小部分,但可能还有更多。当可靠性和准确性是首要需求时,解析锁文件或 SBOM 是前进的方向。

  • engineering
  • security

SafeDep 博客的最新内容

关注以获取关于开源安全与工程的最新更新和见解