开源许可证选型指南:MIT、Apache、GPL 商用边界

开源许可证选型是每个技术团队都躲不过的工程问题:选错了,产品可能被迫开源核心代码;查漏了,一个 GPL 依赖就能让整套商业代码陷入合规风险。本文不谈法条堆砌,只讲工程师视角的实操:MIT、Apache 2.0、GPL 三类许可证的义务边界在哪,什么场景算「分发」,怎么用三行命令扫清依赖家底,以及如何在 CI 里自动拦住高风险许可证。

先说结论:绝大多数团队的合规风险不来自「选哪个许可证」,而来自「不知道自己用了什么」。所以本文后半段的扫描与卡点,比前半段的条款对比更值钱。

一、三大阵营:义务强度决定合规成本

抛开上百种许可证的细节,工程上只需按「著佐权(copyleft)传染强度」分三档记忆。传染强度越高,你把它塞进闭源产品时要付的代价越大。

阵营代表许可证核心义务闭源商用
宽松型MIT、BSD-2/3、ISC保留版权声明与许可证全文可以,几乎无成本
宽松+专利型Apache-2.0保留 LICENSE 与 NOTICE、标注修改、显式专利授权可以,需保留 NOTICE
弱著佐权LGPL、MPL-2.0、EPL-2.0被修改的库/文件本身需以同许可证开源可以,需隔离边界
强著佐权GPL-2.0/3.0分发衍生作品时整体须以 GPL 开源高风险
网络著佐权AGPL-3.0通过网络提供服务也视同分发,须开源SaaS 场景极高风险
源码可见(非开源)SSPL、BUSL-1.1、RSALv2限制以其提供竞品托管服务视条款,通常禁止

最后一行值得单独提醒:SSPL、BUSL 这类协议并未获 OSI 认定为开源许可证,属于「源码可见」。近两年 MongoDB、Terraform、Redis 陆续改用此类协议,社区随即出现 OpenTofu、Valkey 等分叉。这意味着你依赖的组件许可证不是一成不变的——升级一个大版本,许可证可能已经换了。

二、MIT 与 BSD:几乎零义务,但别漏署名

MIT 是最省心的选择:允许商用、修改、闭源分发、再授权,唯一义务是在软件副本或实质部分中保留版权声明和许可证文本。BSD-3-Clause 在 MIT 基础上多一条「不得用原作者名义为衍生产品背书」。

工程上最常踩的坑是:前端项目把上百个 MIT 依赖打包成一个压缩后的 bundle,却没有随包附带任何许可证声明。严格说这已违反署名义务。正确做法是在构建流程里生成一份聚合许可证文件(webpack 的 license-webpack-plugin、Vite 的 rollup-plugin-license 都能自动产出),随产物一起发布。

三、Apache 2.0:真正的价值在专利条款

Apache-2.0 常被误当成「啰嗦版 MIT」,但它多出的两点对企业极其重要。

  • 显式专利授权:贡献者授予你使用其相关专利的权利,并附带「专利反击终止」条款——谁对项目发起专利诉讼,其授权自动终止。这是大厂偏好 Apache-2.0 的主因。
  • NOTICE 与修改标注:若上游带 NOTICE 文件,你分发时必须一并保留;对源码所做的显著修改需在文件中标注。

另有一个历史兼容性细节要记牢:Apache-2.0 与 GPL-2.0 被 FSF 认定为不兼容,与 GPL-3.0 才兼容。所以把 Apache-2.0 代码合进一个 GPL-2.0-only 项目是有问题的,反向亦然。这类判断建议在技术方案评审阶段就写进设计文档,而不是等发版前才发现。

四、GPL 与 AGPL:搞清「分发」这条线

GPL 的传染性触发条件是分发(distribution),不是「使用」。这条线画在哪,直接决定风险大小:

  • 公司内部自用、不对外交付二进制:GPL-2.0/3.0 通常不触发开源义务。
  • 交付客户私有化部署包、发布 App、卖硬件固件:属于分发,衍生作品须以 GPL 开源并提供完整源码。
  • 只提供 SaaS 服务、不给二进制:GPL 一般不触发,但 AGPL-3.0 第 13 条把「通过网络与用户交互」等同于分发——你的 SaaS 后端一旦含 AGPL 组件的衍生代码,就得向使用者提供对应源码。

调用方式不改变许可证性质

常见的自我安慰是「我只是通过命令行调用它,没有链接进来」。判断是否构成衍生作品,关键看结合的紧密程度:静态链接、动态链接、同进程加载通常被视为衍生;通过独立进程 + 标准协议(HTTP、管道)通信,业界普遍认为耦合更松。但这是灰区而非豁免通道,涉及核心商业代码时应让法务定夺。本文是工程实践视角,不构成法律意见。

五、LGPL 与 MPL:可用的中间地带

LGPL 允许闭源程序动态链接其库,条件是用户能替换该库(保留重新链接的可能);你改了库本身,改动部分要以 LGPL 回馈。MPL-2.0 更适合工程落地:传染粒度是文件级——你改了哪个 .go/.rs 文件,只需开源那些文件,新增的独立文件可闭源。

如果你要开源一个希望被商业产品广泛集成、又不想被完全白拿的组件库,MPL-2.0 往往比 GPL 和 MIT 都更贴合诉求。

六、五个高频误区

说法真相
「只要不改代码就没有义务」错。分发即触发署名义务,MIT 也要求随附许可证。
「代码放 GitHub 上就是开源可商用」错。没有 LICENSE 文件默认保留全部权利,法律上你无权商用。
「GPL 依赖只在测试环境用没关系」基本成立,但要确保它不被打进发布产物;devDependencies 需与运行时依赖分开扫描。
「许可证选了就不用管了」错。上游可能变更协议(如 Redis、Terraform),需定期复查。
「AI 生成的代码没有许可证问题」存疑。若模型输出与 GPL 训练语料高度雷同,风险仍在,关键模块建议人工复核。

七、给自己的项目选许可证:三步定稿

决策其实很简单:想要最大采纳率选 MIT;有专利资产或企业协作选 Apache-2.0;想防白拿选 MPL-2.0;想强制生态回馈且不做闭源商业版选 GPL/AGPL(这也是很多公司「开源版 AGPL + 商业授权」双许可策略的基础)。

定稿后落到仓库里的动作是三件事:根目录放 LICENSE 全文、源文件头加 SPDX 标识、包元数据声明许可证字段。

# 1. 拉取标准许可证全文(避免手抄漏字)
curl -sL https://raw.githubusercontent.com/licenses/license-templates/master/templates/mit.txt -o LICENSE

# 2. 源文件头统一加 SPDX 标识(机器可识别,扫描工具依赖它)
# Java / Go / Rust / C 风格:
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 fsdata.site

# Python / Shell 风格:
# SPDX-License-Identifier: MIT

# 3. 包元数据声明(三种生态示例)
# package.json
  "license": "MIT"
# pyproject.toml
  license = { text = "MIT" }
# Cargo.toml
  license = "Apache-2.0 OR MIT"

SPDX 标识不是形式主义:后面所有自动化扫描都靠它和包元数据识别,缺了就只能靠人工比对文本,成本差一个数量级。

八、扫清依赖家底:一条命令查一个生态

合规工作的 80% 是「知道自己用了什么」。各语言都有成熟工具,先跑一遍摸清现状,把结果存档作为基线。

# Node.js:汇总所有依赖许可证分布
npx license-checker --summary --production
# 只允许白名单,出现其他许可证直接非零退出
npx license-checker --production --onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC"

# Python:导出为 Markdown 便于评审归档
pip install pip-licenses
pip-licenses --format=markdown --with-urls --order=license > licenses.md

# Rust:cargo-deny 用配置文件管控(deny.toml 中 allow/deny 列表)
cargo install cargo-deny
cargo deny check licenses

# Java / Maven:聚合第三方许可证清单
mvn license:aggregate-add-third-party

# Go:
go install github.com/google/go-licenses@latest
go-licenses report ./... > licenses.csv

# 跨语言通用:生成 SBOM 后统一分析(容器镜像同样适用)
syft dir:. -o spdx-json=sbom.json
syft packages docker:myapp:latest -o table | grep -iE "gpl|sspl|busl"

第一次扫描出现几十条 GPL/LGPL 命中是常态,别慌。按「是否进入发布产物」分级处理:仅构建期或测试期使用的(编译器、lint 工具、测试框架)风险低;被打进二进制或镜像的才需要逐个评估替换。这份清单本质上就是一笔技术债务,适合登记后按季度偿还,而不是一次性停工整改。

九、在 CI 里设卡点:让风险进不来

人工扫描只能解决存量,增量必须靠自动化。最有效的位置是 PR 检查——新引入的高风险依赖当场被拦住,成本远低于上线前救火。下面这段可直接放进已有的GitHub Actions 流水线

name: license-check
on:
  pull_request:
    paths: ["package.json", "package-lock.json", "pyproject.toml"]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm ci --omit=dev

      # 白名单卡点:出现名单外许可证则 exit 1,PR 变红
      - name: Enforce license allowlist
        run: |
          npx license-checker --production \
            --onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;0BSD;Unlicense;CC0-1.0" \
            --excludePrivatePackages

      # 显式黑名单二次兜底,避免白名单写漏
      - name: Block copyleft
        run: |
          npx license-checker --production --json > lic.json
          if grep -qiE '"(A?GPL-[23]|SSPL|BUSL)' lic.json; then
            echo "::error::检测到强著佐权或源码可见许可证,请评审后再合并"
            exit 1
          fi

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: license-report
          path: lic.json

两点实战经验:一是白名单要配黑名单兜底,因为部分包的 license 字段写法不规范(如 (MIT OR Apache-2.0)),只靠白名单容易误判放行;二是别一上线就设成强制失败,先跑两周只告警的观察期,把历史遗留清干净再开启阻断,否则团队会立刻学会加 skip。同样的思路也适用于pre-commit 钩子——本地就发现问题,比 CI 里发现更省时间。

十、团队落地清单

动作频率产出
全量依赖扫描建立基线一次性licenses.md / sbom.json 存档
CI 白名单 + 黑名单卡点每个 PR阻断新增高风险依赖
发布产物附聚合许可证文件每次发版THIRD-PARTY-NOTICES.txt
复查上游协议变更每季度变更影响评估报告
关键组件双许可/商业授权确认接入前法务确认记录

把这五件事挂到既有流程上即可,不需要新增专职角色:扫描挂 CI,聚合声明挂发布脚本,季度复查挂进版本规划会。若团队还在梳理协作规范,可以顺带把它并入分支模型与发布流程一起定稿。

小结

开源许可证的工程要点可压缩成四句话:宽松型(MIT/Apache)随便用但要署名;Apache-2.0 的专利条款是企业刚需;GPL 看「分发」、AGPL 连 SaaS 都算分发;SSPL/BUSL 根本不是开源,商用前必读条款。而真正降低风险的动作只有两个——先扫清家底建立基线,再用 CI 卡住增量。

如果你打算从使用者转为贡献者,向上游提交代码时也会遇到 CLA/DCO 签署等许可证相关流程,可以继续阅读开源项目贡献指南

上一篇 多智能体协作实战:反思与辩论提升 LLM 可靠性
下一篇 DuckDB 实战:SQL 直查 CSV 与 Parquet