- 区块链
- DeFi
- 金融科技
【免费下载链接】bisq
A decentralized bitcoin exchange network
Bisq 仓库在tools/cve-scan/下内置了一个独立的 OWASP Dependency-Check 扫描工程,专门针对 Gradle 版本目录gradle/libs.versions.toml中声明的全部依赖做已知 CVE 扫描。本文围绕 tools/cve-scan/README.md 展开,结合 tools/cve-scan/build.gradle 与 tools/cve-scan/settings.gradle 的源码实现,讲清这套扫描是如何做到"与主构建完全隔离、依赖清单单点汇聚、结果可门禁化"的,读完你可以直接在自己的环境中复现整套 CVE 扫描流程并理解其节流、门禁与报告机制。
一、为什么要把 CVE 扫描做成独立工程
Bisq 的主构建对依赖安全有非常严格的要求:gradle/README.md 说明gradle/目录下维护着完整的依赖校验体系——verification-metadata.xml 记录每个依赖构件的 SHA-256 校验和与 PGP 签名,verification-keyring.keys 保存签名验证用的公钥,另有 dependency-checksum-fallback-allowlist.tsv 处理仅用校验和兜底的构件。任何会污染这条校验链或构建类路径的工具,接入成本都很高。
因此 CVE 扫描被刻意设计为一个独立 Gradle 工程,README 明确列出它的三条隔离承诺:
- 不修改
gradle/verification-metadata.xml; - 不触碰主构建的 build classpath;
- 不引入任何运行时依赖——扫描工具自身的传递依赖只存在于
tools/cve-scan/子目录内。
这一点从根 settings.gradle 可以得到印证:主构建include了proto、core、desktop、seednode等十多个子项目,并通过includeBuild 'bitcoind'引入独立构建,但没有包含tools/cve-scan。它只能以-p tools/cve-scan的方式单独运行,与主构建共享仓库,却不共享项目模型。
二、工程结构:settings.gradle 如何读取主仓库的版本目录
tools/cve-scan/settings.gradle 只有 13 行,核心是dependencyResolutionManagement块:
rootProject.name = 'cve-scan' dependencyResolutionManagement { repositories { mavenCentral() maven { url 'https://jitpack.io' } } versionCatalogs { libs { from(files('../../gradle/libs.versions.toml')) } } }关键设计在于from(files('../../gradle/libs.versions.toml')):这个独立工程直接复用主仓库的版本目录文件,而不是维护一份拷贝。仓库的依赖版本全部集中在 gradle/libs.versions.toml 中集中声明,例如bouncycastle = { strictly = '1.84' }、grpc = { strictly = '1.81.0' }、protobuf = { strictly = '3.25.8' },以及bitcoinj、netlayer等来自com.github.bisq-network的 JitPack 构件——这也解释了 settings.gradle 里为何要同时声明 Maven Central 和 jitpack 两个仓库:扫描时这些依赖必须真实可解析,JitPack 来源的库走不了 Maven Central。
版本目录的[versions]表用strictly约束锁死版本,[libraries]表把别名映射到group:artifact坐标(如bouncycastle-bcpg-jdk18on、grpc-core、zxing-core等),还定义了[bundles](如jersey-libs)和[plugins](protobuf 插件)。CVE 扫描的输入正是这份单一事实来源。
三、扫描输入:把版本目录里每个库都变成依赖
tools/cve-scan/build.gradle 的前半部分做了两件事:
plugins { id 'java' id 'org.owasp.dependencycheck' version '12.1.0' } def catalog = extensions.getByType(VersionCatalogsExtension).named('libs') dependencies { catalog.libraryAliases.each { alias -> implementation catalog.findLibrary(alias).get() } }- 引入
org.owasp.dependencycheck插件(固定版本 12.1.0)和java插件; - 通过
VersionCatalogsExtension拿到名为libs的版本目录,遍历其全部库别名,逐个加进implementation配置。
这样做的效果是:扫描范围自动等于[libraries]表的全部条目。新增依赖只需要在主仓库的版本目录里加一行,下次运行 CVE 扫描时新依赖就自动进入检查范围,无需改动扫描工程本身。README 的 Scope 一节也确认了这一点:扫描覆盖[libraries]表每一项,包括传递依赖(OWASP Dependency-Check 的dependencyCheckAggregate任务会解析完整依赖图并对每个构件做 CVE 比对)。
有两类依赖不在扫描范围内,README 明确交代:
- 以原始
"group:artifact:version"字符串写在各子项目build.gradle里的依赖——Bisq 把所有依赖集中在版本目录,因此"预期不存在"这类写法; bitcoind这个 included build。从根 settings.gradle 可以看到它通过includeBuild 'bitcoind'以独立 Gradle 构建接入,不经过 cve-scan 的版本目录加载路径,因此不在覆盖范围内。
四、运行扫描:cveCheck 任务与 24 小时节流机制
README 给出的三条运行命令都从仓库根目录执行:
# Throttled scan: skips if last run <24h ago AND libs.versions.toml unchanged ./gradlew -p tools/cve-scan cveCheck # Force re-scan (ignore throttle) ./gradlew -p tools/cve-scan cveCheck --rerun-tasks # Direct task (same throttle applies) ./gradlew -p tools/cve-scan dependencyCheckAggregatecveCheck是一个自定义包装任务,build.gradle 第 76-80 行 定义了它:
tasks.register('cveCheck') { group = 'verification' description = 'Run CVE scan (throttled: skips if <24h old and libs.versions.toml unchanged).' dependsOn 'dependencyCheckAggregate' }真正的节流逻辑挂在dependencyCheckAggregate任务上,见 build.gradle 第 60-74 行:
def cveMarker = layout.buildDirectory.file('cve-check.ok').get().asFile def versionsFile = file('../../gradle/libs.versions.toml') def cveTtlMs = 24L * 60 * 60 * 1000 tasks.named('dependencyCheckAggregate').configure { onlyIf { if (!cveMarker.exists()) return true if (versionsFile.exists() && versionsFile.lastModified() > cveMarker.lastModified()) return true return (System.currentTimeMillis() - cveMarker.lastModified()) > cveTtlMs } doLast { cveMarker.parentFile.mkdirs() cveMarker.text = "ok ${new Date()}\n" } }可以推断其完整判定语义:
- 首次运行:
tools/cve-scan/build/cve-check.ok标记文件不存在,onlyIf返回 true,正常扫描; - 依赖变了:
libs.versions.toml的修改时间新于标记文件,立即重扫——也就是说,只要有人升级了依赖版本,节流自动失效; - TTL 窗口:距上次成功扫描不足 24 小时且依赖未变,任务被跳过(
cveTtlMs = 24h就是 README Configuration 一节提到的可调节流窗口); - 成功落标记:任务执行完毕后写入
cve-check.ok并盖上时间戳; - 强制重扫:
--rerun-tasks会忽略 Gradle 的 up-to-date 判定,绕过onlyIf的跳过路径。
这个设计的价值在于:NVD 数据每日更新、依赖版本又很少变化,每天最多真正执行一次全量 CVE 扫描,既省时间又省 NVD 配额。
五、NVD API Key:从"十分钟起步"到"秒级同步"
OWASP Dependency-Check 的 CVE 数据来自 NVD(National Vulnerability Database)。README 的 NVD API key 一节给出了明确的使用建议:
- 不带 key 时 NVD API 节流非常激进,首次运行可能超过 10 分钟;
- 带 key 后同步只需几秒;
- key 需在 NVD 官方开发者页面申请(README 给出入口
https://nvd.nist.gov/developers/request-an-api-key,属外部站点,此处不再贴链接); - NVD 数据feed缓存在
~/.gradle/dependency-check-data,跨运行复用。
传递 key 的两种方式:
export NVD_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # or ./gradlew -p tools/cve-scan cveCheck -PnvdApiKey=xxxx两种传法在源码中的对应实现见 build.gradle 第 38-45 行:
def nvdKey = System.getenv('NVD_API_KEY') ?: (project.findProperty('nvdApiKey') ?: '') nvd { if (nvdKey) { apiKey = nvdKey } // Re-sync NVD feed at most once every 24h. validForHours = 24 }取值顺序是:环境变量NVD_API_KEY优先,其次项目属性-PnvdApiKey,都没有则留空。另外validForHours = 24让 NVD feed 本身也遵循"每天最多重新同步一次"的策略,与cveTtlMs的 24 小时节流窗口在节奏上保持一致。
六、门禁与报告:failBuildOnCVSS = 0 的硬约束
README 明确:Build fails with non-zero exit on any CVE(failBuildOnCVSS = 0),即任何 CVSS 评分 ≥ 0 的 CVE 都会让构建以非零码失败。在 build.gradle 第 34-36 行 中:
dependencyCheck { failBuildOnCVSS = 0 formats = ['HTML', 'JSON'] ... }报告输出到tools/cve-scan/build/reports/dependency-check-report.html,同时生成同名 JSON(对应formats配置)。README 给出的调参建议:
failBuildOnCVSS = 0—— 任何 CVE 都失败;想放宽到"只拦高危"可提高到例如7,允许 low/medium 通过;formats = ['HTML', 'JSON']—— 可按需追加'SARIF'、'XML'等格式,方便接入 CI 生态;- TTL
cveTtlMs—— 节流窗口,默认 24 小时。
对 Bisq 这样的开源交易所项目,"零 CVE 容忍"的门禁策略配合strictly版本锁定,构成了供应链安全的最后一道静态防线:版本升级(libs.versions.toml变化)会自动触发重扫,任何引入已知漏洞的版本都会在门禁处被拦下。
七、分析器调优:为什么禁用 assembly、Nuspec 与 OSS Index
build.gradle 第 47-57 行 针对 Bisq 的技术栈做了分析器裁剪:
analyzers { // Java-only project — disable irrelevant or rate-limited analyzers. assemblyEnabled = false // .NET dll/exe scanner (tor-binary jars trip this) nuspecEnabled = false nugetconfEnabled = false ossIndex { // Anonymous OSS Index rate-limits aggressively → every jar errors and // the task fails. NVD already covers the same CVE data. enabled = false } }逐条对应 README 隐含的意图:
assemblyEnabled = false:OWASP 的 Assembly 分析器面向 .NET 的 dll/exe 包,而 Bisq 依赖中的 tor 二进制 jar(版本目录里可见netlayer-tor-external、netlayer-tor-native两个 Tor 相关构件)会让它误报,Java 项目直接关掉;nuspecEnabled/nugetconfEnabled:项目不含 .NET 依赖,NuGet 分析器无意义;ossIndex.enabled = false:匿名调用 OSS Index 限流极严,会导致每个 jar 都报错并使任务整体失败;而 NVD 数据源已经覆盖相同的 CVE 信息,重复扫描收益为零。
这些都是"从源码注释与配置结构看"得出的工程取舍:宁可关停用不上的数据源,也不让限流把整条扫描流水线拖垮。
八、边界与 CI 集成方式
README 的 Scope 与 CI 两节划定了这套工具的适用边界:
- 扫描范围:
[libraries]表全部条目 + 传递依赖;子项目build.gradle中的裸坐标依赖不在范围(项目约定所有依赖集中在版本目录,因此"预期不存在"); - 不覆盖:
bitcoindincluded build(独立 Gradle 构建,见根 settings.gradle 的includeBuild 'bitcoind'); - CI 策略:该工具被有意设计为 local-only,不绑定特定 CI 平台。若要定时执行,只需在 CI workflow 中调用
./gradlew -p tools/cve-scan cveCheck——节流标记文件与validForHours = 24会自然把频率压到每天一次量级。
配合仓库既有体系看,这套 CVE 扫描与 gradle/README.md 描述的依赖校验(checksum + PGP 签名,报告见 docs/dependency-signature-report.md)是互补关系:前者保证"拿到的构件是发布方签过名的、内容未被篡改",后者保证"所选版本不携带已知漏洞"。两者都建立在依赖集中声明于gradle/libs.versions.toml这一单一事实来源之上,这也是整个供应链安全流程可以低成本自动化(依赖一变动即触发 CVE 重扫)的根本原因。
小结
tools/cve-scan以 13 行 settings.gradle + 80 行 build.gradle 的极小体量,实现了 Bisq 依赖链的 CVE 门禁:通过版本目录别名遍历把[libraries]全表变成扫描输入,用cve-check.ok标记 + 24 小时 TTL + 依赖文件 mtime 比较实现智能节流,用failBuildOnCVSS = 0把任何 CVE 变成构建失败,并以 HTML/JSON 双格式报告输出到tools/cve-scan/build/reports/。整套设计与主构建的依赖签名校验完全解耦,为大型多模块 Java 项目提供了一个可复制的"依赖清单单点汇聚 + 独立 CVE 门禁"参考实现。
- 区块链
- DeFi
- 金融科技
【免费下载链接】bisq
A decentralized bitcoin exchange network
相关推荐
One-Core-API:让老旧的Windows XP/2003系统也能运行现代软件的终极兼容方案
One Core API:让老旧的Windows XP/2003系统也能运行现代软件的终极兼容方案 还在为Windows XP或Windows Server 2
操作系统系统编程驱动开发OWASP dependency-check 的 Go Mod Analyzer:基于 go mod 的 Go 依赖漏洞扫描指南
OWASP dependency check 的 Go Mod Analyzer:基于 go mod 的 Go 依赖漏洞扫描指南 本指南围绕 OWASP dep
应用安全供应链安全漏洞扫描开发工具OWASP dependency-check 数据目录缓存实战:用 GitHub Actions 加速 Maven 漏洞扫描
OWASP dependency check 数据目录缓存实战:用 GitHub Actions 加速 Maven 漏洞扫描 OWASP dependency
应用安全供应链安全漏洞扫描开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考