软件自由保护协会(SFC)发了一篇近万字的声明,态度非常明确——Bambu Lab(拓竹)从 2020 年起持续违反 AGPLv3 许可证义务,SFC 不再停留在口头呼吁,而是要动手逆向工程,拿到源码对照的事实证据,正面解决这个问题。
这不是一场口水战,而是一次许可证合规执法的技术行动。对任何在产品中使用开源组件的团队来说,这件事的走向都值得认真关注。
AGPLv3 到底要求什么
GPLv3 的核心义务是:分发二进制时,必须同时提供完整对应源码(包括构建脚本、依赖声明)。AGPLv3 在此基础上加了一条——即使你不分发二进制,只要用户通过网络与你的修改版交互(比如访问一个运行修改版的服务),你就必须向这些网络用户提供源码。
拓竹的 3D 打印机固件和网络服务中使用了 AGPLv3 覆盖的组件。打印机本身是商业硬件产品,固件随设备分发;同时拓竹的云服务让用户通过网络与设备交互。这两条路径都触发 AGPLv3 的源码提供义务。
问题在于:拓竹提供的源码要么不完整(缺少构建所需的工具链配置、补丁),要么干脆没提供。四年下来,社区反复追问,拓竹的回应始终没有满足许可证的文字要求。
SFC 为什么选择逆向工程
许可证执法的难点在于举证。你不能只说"我觉得你没给完整源码"——你需要证明:用户拿到的源码无法构建出与分发二进制功能等价的产物。
这就是逆向工程的用武之地:
- 从设备固件中提取二进制,反汇编/反编译,还原符号表和函数逻辑。
- 将还原结果与拓竹公布的"源码"逐项对照——缺失哪些模块、哪些编译选项被省略、哪些依赖版本不一致。
- 形成一份技术报告:源码 A + 构建指令 B → 二进制 C;而拓竹提供的源码 A' + 构建指令 B' → 二进制 C',C ≠ C',因此源码不"完整对应"。
SFC 在声明中明确表示,他们将组织技术人员对拓竹设备进行逆向分析,用事实数据支撑合规主张。这比发律师函更硬核——因为证据来自技术验证,而非法律推论。
实践:用工具扫描自己项目的许可证风险
无论你站在哪一边,这件事提醒所有团队:如果你的产品包含 AGPL/GPL 组件,合规不是可选的。下面是一个可以直接跑的扫描流程,帮你快速摸底。
用 pip-licenses 审计 Python 项目的许可证分布
# 安装审计工具
pip install pip-licenses
# 导出当前环境所有依赖的许可证清单,标记高风险项
pip-licenses --format=csv --from-system=all \
| awk -F',' '$4 ~ /AGPL|GPL/ {print $1, $2, $4}' \
> agpl_gpl_deps.txt
# 查看结果
cat agpl_gpl_deps.txt
输出类似:
requests-toolbelt 0.9.1 Apache-2.0
# 如果有 AGPL 依赖,会直接列出:
some-agpl-lib 1.2.3 AGPL-3.0
用 licensecheck 扫描 Node.js 项目
# 安装
npm install -g licensecheck
# 扫描并过滤 GPL/AGPL
licensecheck --csv | grep -iE 'agpl|gpl'
用 shell 脚本做更细致的合规自查
下面这个脚本会扫描指定目录下所有 Python 源文件的许可证头,标记包含 AGPL/GPL 声明的文件:
#!/usr/bin/env python3
"""scan_license_headers.py — 扫描目录中源文件的许可证声明,标记 GPL/AGPL"""
import os, re, sys
TARGET_DIR = sys.argv[1] if len(sys.argv) > 1 else "."
GPL_PATTERN = re.compile(r'(AGPL|GPL)[-\s]*(v?\d[\.\d]*)?', re.IGNORECASE)
results = []
for root, dirs, files in os.walk(TARGET_DIR):
# 跳过常见非源码目录
dirs[:] = [d for d in dirs if d not in ('.git', 'node_modules', '__pycache__', 'venv')]
for fname in files:
if not fname.endswith(('.py', '.js', '.ts', '.go', '.rs')):
continue
fpath = os.path.join(root, fname)
try:
with open(fpath, encoding='utf-8', errors='ignore') as f:
head = f.read(2048) # 只读前 2KB,许可证头通常在文件开头
except Exception:
continue
match = GPL_PATTERN.search(head)
if match:
results.append((fpath, match.group(0)))
if results:
print(f"⚠ 发现 {len(results)} 个 GPL/AGPL 覆盖的源文件:")
for path, decl in results:
print(f" {path} → {decl}")
else:
print("✅ 未在源文件头中发现 GPL/AGPL 声明")
# 进一步检查:这些文件是否在产品分发或网络服务中使用?
if results:
print("\n下一步:确认上述文件是否随产品分发或通过网络提供服务。")
print("如果是 → 必须提供完整对应源码(AGPL 还要求网络用户提供途径)。")
print("如果只是内部开发工具且不分发 → 风险较低,但仍建议标注。")
运行:
python3 scan_license_headers.py /path/to/your/project
合规的几个关键判断点
从 SFC 与拓竹的冲突中,可以提炼出几条对所有团队都适用的判断规则:
分发即触发义务。 无论你卖的是硬件、固件镜像还是 Docker 镜像,只要用户拿到了包含 GPL/AGPL 组件的二进制,源码义务就启动了。"我们只是用了库的接口"不构成豁免。
AGPL 的网络交互条款比 GPL 更激进。 即使你不分发二进制,只要用户通过网络与你的修改版交互(API、Web UI、远程控制),你就必须向这些用户提供源码获取途径。拓竹的云服务显然落入这个范围。
"完整对应源码"有严格定义。 GPLv3/AGPLv3 第 1 条明确:源码必须包含构建脚本、配置文件、依赖版本信息,且能够构建出与分发二进制功能等价的产物。只丢一个 tarball 上 GitHub 而缺少构建指令,不满足要求。
逆向工程在许可证执法中是合法手段。 SFC 的声明引用了相关法律框架,说明为了验证合规性而对分发二进制进行逆向分析,属于正当的技术取证行为。这对社区维权是一个重要信号——你不需要依赖厂商的"自觉",技术手段可以独立验证。
给团队的 Checklist
如果你的产品(硬件或 SaaS)中包含 GPL/AGPL 组件,建议立即做以下检查:
- 依赖审计:用上面的脚本或
pip-licenses/licensecheck扫描全部依赖,列出所有 GPL/AGPL 组件及其版本。 - 分发路径梳理:哪些组件随产品分发?哪些通过网络向用户提供服务?分别标注 GPLv3 和 AGPLv3 的触发点。
- 源码完整性验证:对每个触发义务的组件,检查你是否提供了完整对应源码——包括构建脚本、工具链版本、依赖锁定文件。自己跑一遍构建流程,确认产物与分发二进制一致。
- 源码获取途径:AGPLv3 要求网络用户也能便捷获取源码。产品界面或文档中是否有明确的源码获取链接?
- 修改声明:如果你对 GPL/AGPL 组件做了修改,必须在源码中标注修改内容和日期。
合规不是事后补票,而是产品发布前的硬性检查。SFC 对拓竹的行动说明:社区组织有技术能力和法律意愿去追究长期违规,四年不处理不代表义务消失。