4. 开源代码引入审查

开源代码引入审查不是一次性的“扫一遍”,而是一条从“代码进入仓库之前”到“上线之后持续跟踪”的链路。它的核心目标有三个:第一,搞清楚项目里到底用了哪些第三方组件;第二,搞清楚这些组件带着什么许可证义务;第三,搞清楚这些组件当前有没有已知漏洞,以及漏洞是否真的影响我们的使用方式。下面按这条链路的实际执行顺序展开。

一、第三方组件识别:先解决“看不见”的问题

组件识别的难点在于,依赖往往不是显式声明的。一个 Java 项目可能通过 Maven 传递依赖引入几十个间接组件;一个前端项目可能通过 npm 的嵌套依赖引入数百个包;一个 C/C++ 项目可能把第三方源码直接复制进 third_party/ 目录,既没有包管理器记录,也没有版本号。因此识别要分三层做:

  • 包管理器清单识别:读取 pom.xml、build.gradle、package-lock.json、go.mod、requirements.txt、Cargo.lock 等文件,得到直接依赖和锁定版本。
  • 传递依赖展开:用包管理器自带命令展开完整依赖树,例如 mvn dependency:tree、npm ls --all、go mod graph,得到间接依赖及其版本。
  • 源码级识别:对没有包管理器记录的目录,用许可证扫描工具或指纹比对识别文件来源,重点检查 vendor/、third_party/、external/ 等目录。

识别结果要落到“组件名 + 版本 + 来源 + 引入方式”四个字段上。缺少版本号时,不能简单标记为“未知”,而应通过文件头注释、VERSION 文件、Git 提交记录或哈希比对尽量定位到具体版本,否则后续漏洞关联会失去基础。

二、许可证扫描:识别义务,而不是只识别名称

许可证扫描要回答的是:这个组件采用什么许可证?它要求我们做什么?常见许可证可以按义务强度粗略分为三类:宽松型(MIT、BSD、Apache-2.0)、弱 copyleft(LGPL、MPL)、强 copyleft(GPL、AGPL)。扫描工具通常输出 SPDX 标识符,例如 MIT、Apache-2.0、GPL-3.0-only、LGPL-2.1-or-later。

实际审查中,比“识别出许可证名称”更重要的是判断义务是否可履行。例如 Apache-2.0 要求保留版权声明、许可证文本和 NOTICE 文件;GPL-3.0 要求衍生作品整体以 GPL 发布并提供对应源码;AGPL 还会在网络服务场景下触发源码提供义务。如果项目是闭源商业软件,引入 GPL 组件通常需要走例外审批或替换方案,而不是简单记录后放行。

许可证扫描结果必须人工复核。工具可能把同一组件识别出多个许可证(如 MIT OR Apache-2.0),也可能把文件头注释误判为许可证。对 copyleft 类结果,建议由法务或开源合规负责人确认后再进入发布流程。

三、依赖分析与 SBOM 生成:把清单变成可追踪资产

依赖分析的目标是形成一张完整的依赖关系图,明确“谁依赖谁、版本是否冲突、是否存在重复引入”。例如同一个 log4j-core 可能被两个不同路径引入,版本分别为 2.14.1 和 2.17.1,此时需要判断最终打包进产物的是哪个版本。依赖分析还要关注依赖范围:compile、runtime、test、provided 对最终产物和漏洞影响范围不同,测试依赖通常不进入发布产物,但仍可能影响构建环境安全。

SBOM(软件物料清单)是把识别和分析结果固化成标准格式的交付物。常用格式有 SPDX 和 CycloneDX。一个最小可用的 CycloneDX 片段如下:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "components": [
    {
      "type": "library",
      "name": "log4j-core",
      "version": "2.17.1",
      "purl": "pkg:maven/org.apache.logging.log4j/log4j-core@2.17.1",
      "licenses": [{ "license": { "id": "Apache-2.0" } }]
    }
  ]
}

SBOM 的关键字段是组件名、版本、包唯一标识(purl 或 CPE)和许可证。没有 purl/CPE,漏洞库就无法准确匹配;没有版本,就无法判断漏洞是否在影响范围内。SBOM 应在每次构建时自动生成并随版本归档,而不是发布前临时补一份。

四、漏洞关联:从“有漏洞”到“是否受影响”

漏洞关联是把 SBOM 中的组件与漏洞数据库(如 NVD、OSV、GitHub Advisory)做匹配。匹配结果通常给出 CVE 编号、CVSS 评分、影响版本范围和修复版本。但“组件有 CVE”不等于“我们的产品受影响”,还要判断三点:

  1. 版本是否落在影响范围内:例如某漏洞影响 >=2.0.0, <2.17.0,而项目实际使用 2.17.1,则不受影响。
  2. 代码路径是否可达:漏洞函数是否被项目实际调用。不可达的漏洞风险等级可以下调,但仍应记录。
  3. 运行配置是否触发:例如反序列化漏洞只有在开启特定功能或接收不可信输入时才可利用。

下面这张图展示了从代码引入到漏洞关联的完整审查链路,以及每个环节的关键输出物和判断点。

开源代码引入审查链路:识别 → 扫描 → 分析 → SBOM → 漏洞关联 ① 组件识别 pom / lock / go.mod 依赖树展开 源码目录指纹比对 ② 许可证扫描 SPDX 标识符 宽松 / 弱 copyleft 强 copyleft 义务判断 ③ 依赖分析 依赖关系图 版本冲突 / 重复引入 compile / test 范围 ④ SBOM 生成 CycloneDX / SPDX name + version + purl 随构建归档 ⑤ 漏洞关联 NVD / OSV 匹配 版本范围判断 可达性分析 输出:组件清单 输出:许可证义务表 输出:依赖关系图 输出:SBOM 文件 输出:漏洞影响清单 漏洞关联判断三问 Q1 版本是否落在影响范围? → 否:记录但不阻断 Q2 漏洞函数是否可达? → 否:降级为低风险 Q3 运行配置是否触发? → 是:进入修复或缓解流程 审查结论:放行 / 有条件放行 / 阻断 有条件放行需记录缓解措施与复评时间 持续跟踪 SBOM 随版本归档,新 CVE 发布后重新关联 工具链建议 SCA 工具 + CI 门禁 + 人工复核

五、把审查嵌入研发流程

上述链路如果只在发布前执行一次,价值会大打折扣。更实际的做法是把它拆进 CI:在代码合并请求阶段运行组件识别和许可证扫描,发现强 copyleft 或高风险许可证时阻断合并;在构建阶段自动生成 SBOM 并归档;在发布后定期用最新漏洞库重新关联 SBOM,发现新漏洞时自动创建修复任务。

一个可参考的门禁规则示例如下:

检查项触发条件处理动作
许可证类型识别到 GPL-3.0 / AGPL-3.0阻断合并,转法务审批
漏洞等级CVSS ≥ 7.0 且函数可达阻断发布,要求修复或缓解
SBOM 完整性组件缺少 purl 或版本号标记待补充,不阻断但限期修复
依赖范围test 依赖引入高危漏洞记录并评估构建环境风险

最后要强调的是,SBOM 和漏洞关联不是“交差文档”。它们的真正价值在于:当某个组件爆出严重漏洞时,团队能在几分钟内回答“我们哪些产品、哪些版本用了它、是否可达、是否需要紧急发版”。能做到这一点,开源代码引入审查才算真正落地。