jjuhric/cvetrace-go

GitHub: jjuhric/cvetrace-go

一款零运行时依赖的 Go 语言 CVE 漏洞扫描器,支持多生态依赖检测并提供优先级排序和修复建议。

Stars: 0 | Forks: 0

# cvetrace-go [jjuhric/cvetrace](https://github.com/jjuhric/cvetrace) 的 Go 移植版 —— 一个 CVE **发现、追踪和解决方案** 的 CLI。理念相同,目标不同:此版本 编译为单一、完全静态的二进制文件,内部不捆绑任何运行时,无需安装任何东西即可运行,这与需要安装 Node 的 Node.js 原版(或像 Bun 这样将整个 JS 运行时打包进二进制文件,导致体积大得多的编译 JS 方法)不同。 **刚接触 Go?** 请参阅 [GO_PRIMER.md](GO_PRIMER.md) —— 这是一份概念图,将你在此项目的 Node 版本 (JavaScript) 中已了解的知识映射到 Go,并指出了每个概念在此实际代码库中出现的位置。源代码本身的注释也比通常更详细,专门用于解释 Go 惯用法。 ## 为什么特别选择 Go Go 直接编译为原生的、特定于操作系统的二进制文件,运行不需要单独的运行时 —— 与 C/C++/Rust 同属一类,比使用 Bun/Deno/Node 的 SEA 功能编译 JS 代码库(仍然将整个语言运行时打包到输出中,产生大得多的文件)高出一个层级。在这个特定项目中选择 Go 而不是 C/C++/Rust,是因为其标准库已经涵盖了 CVE 扫描器所需的一切 —— HTTP 客户端 (`net/http`)、JSON (`encoding/json`)、正则表达式、目录遍历 —— 无需任何第三方依赖,并且因为它可以从一台机器交叉编译为每个操作系统,且零额外工具 (`GOOS=windows go build`,无需调整工具链)。 ## 环境要求 | 场景 | 需要什么 | |---|---| | 运行预构建的二进制文件 | 什么不需要。这就是重点 —— 请参阅 [安装预构建的二进制文件](#installing-a-prebuilt-binary)。 | | 从源码构建/运行 | 已安装 [Go](https://go.dev) 1.26+ | | 无论哪种方式 | 始终需要对 `api.osv.dev`(漏洞查询)的出站互联网访问。此外还需要安装 Java(用于 Gradle 本身),但仅当目标项目实际具有 `build.gradle`/`.kts` 时才需要 —— 否则不相关。 | ## 安装预构建的二进制文件 每个匹配 `v*.*.*` 的标签都会触发 [`.github/workflows/release.yml`](.github/workflows/release.yml),它为每个支持的操作系统/架构交叉编译此项目,并将二进制文件发布到该标签的 [GitHub Release](https://github.com/jjuhric/cvetrace-go/releases) —— 不需要 `go` 工具链,不需要 Node,在最终运行它的机器上无需安装任何东西。 1. 从[最新发布](https://github.com/jjuhric/cvetrace-go/releases/latest)下载与你的操作系统/架构匹配的二进制文件: `cvetrace-windows-amd64.exe`, `cvetrace-darwin-amd64`, `cvetrace-darwin-arm64`, `cvetrace-linux-amd64`, 或 `cvetrace-linux-arm64`。 2. 在 macOS/Linux 上,将其标记为可执行文件:`chmod +x cvetrace-*` 3. 直接运行它:`./cvetrace-darwin-arm64 scan ` (或者在 Windows 上只需运行 `cvetrace-windows-amd64.exe scan `)。 每次发布还会在二进制文件旁发布一个 `checksums.txt` (SHA-256),以便你 在运行下载的文件之前进行验证:`sha256sum -c checksums.txt`(或者与你下载的文件进行手动对比)。 `cvetrace --version` 会报告下载的二进制文件来自的确切发布版本 —— 请参阅 [`internal/cli/cli.go`](internal/cli/cli.go) 中的 `Version`,了解发布工作流如何在构建时将其植入其中。 ## 用法 ``` git clone https://github.com/jjuhric/cvetrace-go cd cvetrace-go go run ./cmd/cvetrace scan [options] ``` 或者构建一次二进制文件并重复使用它: ``` go build -o cvetrace ./cmd/cvetrace ./cvetrace scan [options] ``` - `` — 要扫描的目录。检测 Node (`package.json`/ `package-lock.json`), Java/Maven (`pom.xml`), Java/Gradle (`build.gradle`/`.kts`,通过实际调用目标项目自己的 Gradle wrapper), 以及 Python (`Pipfile.lock`/`requirements.txt`/`pyproject.toml`) —— 请参阅 [目前已实现的功能](#whats-implemented-so-far)以获取具体涵盖的内容。 - `--json` — 输出机器可读的 JSON 报告,而不是终端报告。 - `--fail-on ` — 如果发现达到或超过此严重程度的漏洞,则以非零状态退出 (`low`, `moderate`/`medium`, `high`, `critical`)—— 这是 CI pipeline 依赖的标志。被忽略的发现绝不会计入此项。 - `--exclude ` — glob 模式(相对于 ``),例如 `'test/**'`, 在依赖发现和代码使用扫描期间都要跳过 —— 可重复使用。 - `--ignore ` — 在此次运行中忽略特定的 CVE/GHSA/etc. id —— 可重复使用。如需 永久忽略,可以在扫描的目录中添加一个 `.cvetraceignore` 文件 (请参阅[忽略发现](#ignoring-findings))。 运行 `cvetrace scan -h` 获取完整的选项参考(示例、退出状态和 每个报告字段的说明)。 **Go 注意事项:** 与 Node 版本的 CLI(基于 commander.js 构建,可在任何地方接受 flags)不同,Go 的标准 `flag` 包通常要求 flags 出现在任何位置参数*之前*。本项目绕过了这一点(请参阅 `internal/cli/cli.go` 中的 `reorderFlagsFirst`),因此 `cvetrace scan --json` 和 `cvetrace scan --exclude test/**` 的工作方式与 Node 版本相同 —— 但如果你查看代码,这正是值得注意的那类陷阱。更多 详情请见 [GO_PRIMER.md](GO_PRIMER.md)。 ## 忽略发现 有两种方法可以忽略已经过审查并接受的发现,使其不再显示(镜像 Nexus IQ/Dependabot 的“忽略”功能): - `--ignore ` — 一次性的,仅限此次运行。 - 在要扫描的目录中放入一个 `.cvetraceignore` 文件 — 永久生效:每行一个 CVE/GHSA/etc. id,忽略空行和整行的 `#` 注释,记录中会捕获可选的行尾 `# reason`。 ``` # .cvetraceignore CVE-2021-1234 # no reason given CVE-2021-5678 # reviewed, false positive in our usage ``` 被忽略的发现会从主报告中删除,并且不计入 `--fail-on`, 但绝不会默默丢弃它们 —— 使用 `--json` 运行以查看完整的 `ignored` 数组 (id、原因以及匹配的机制 —— `.cvetraceignore` 或 `--ignore`)。请参阅 `internal/trace/ignore.go`。 ## 推荐的 agent 工作流 无论是人类还是 AI 编码 agent,都应该以相同的方式运行 cvetrace-go —— `cvetrace scan --json` —— 但在项目 IDE 内工作的 agent (GitHub Copilot, Claude Code/Cowork, Gemini Antigravity 等) 可以更进一步,直接对 报告采取行动。预期的循环: 1. 运行 `cvetrace scan . --json` (视情况添加 `--exclude`/`--ignore`/`--fail-on`) 并读取 `vulnerabilities` 数组 —— 已经按 `priorityScore` 排序,最高者 居首。按此顺序依次处理。 2. 对于每个发现,根据 `remediationTier` 进行分支处理: - **`safe-to-update`** — 直接应用它。在 `manifestPath` 指定的文件中,将包升级到 `recommendedVersion` (或者用 `fixedVersion`,仅解决这一个 CVE),或者 —— 如果 `dependencyScope` 为 `transitive` —— 改为应用 `overrideSnippet` (它指明了确切的文件并给出了具体的代码片段:npm `overrides`, Gradle `resolutionStrategy.force`, 或 Maven `dependencyManagement`)。然后运行 项目自有的 install/build/test 步骤以确认没有破坏任何东西,并重新运行 `cvetrace scan` 确认该发现已消失,然后再进行下一步。 - **`needs-approval`** — 暂时不要做任何更改。总结该发现(包, 当前版本 → 目标版本,为什么它是 major/breaking 升级)并提出简短的 实施计划 —— 需要修改哪些文件,可能会破坏什么,你将如何验证 —— 然后等待用户明确批准后再触及任何代码。 - **`no-fix-available`** — 目前还没有能解决此问题的版本升级。阅读 `advisoryDetails` 获取缓解/变通方法(例如配置标志),并提出 该方案,或者如果不存在变通方法,仅将其标记以引起用户注意。 - **`unknown-impact`** — 当作 `needs-approval` 同样处理:cvetrace 无法 确认版本跳跃的幅度,因此不要假设它是安全的。 3. 在反馈时,使用 `priorityScore`/`priorityLabel`, `usageContext`, 和 `codeReference` 来解释*为什么*某项发现会排在那个位置 —— 例如,“此 CRITICAL CVE 为 P4 是因为它是仅在 dev 中的依赖,且未找到代码引用。” 4. 如果用户说跳过某项发现,记录该决定而不是 下次直接不提它:将它的 id 添加到项目中的 `.cvetraceignore` 文件中 (并加上说明由谁决定以及原因的 `# reason`)。 这是对预期工作流的描述,而不是 cvetrace-go 自身强制执行的 —— 如果你希望在该项目中工作的 agent 自动遵循它,请将其复制(或 改编)到你自己的项目的 agent 指令文件中 (`CLAUDE.md`, `AGENTS.md`, `.github/copilot-instructions.md` 等)。 ## 工作原理 1. **发现** (`internal/discover`) — 遍历目标目录,跳过 `node_modules`/`.git`/`target`/etc.,并将每个生态系统的 manifest 解析为 已解析的 `{ecosystem, name, version}` 依赖项:Node 的 `package.json`/ `package-lock.json` (`node.go`), Maven 的 `pom.xml`(通过 Go 的内置 XML 解析器) (`java.go`), Gradle 的 `build.gradle`/`.kts`(通过使用生成的 init script 实际调用目标项目 自己的 Gradle wrapper)(`gradle.go` —— 与 Node 版本使用的 “完全解析,而非静态解析”方法相同,如果无法调用 Gradle,则使用基于正则表达式的静态解析作为后备),以及 Python 的 `Pipfile.lock`/`requirements.txt`/`pyproject.toml` (`python.go`)。 2. **追踪** (`internal/trace`) — 批量向 [OSV.dev](https://osv.dev)(免费,无需 API key)查询每个依赖项,针对当前版本实际落入的*特定* 受影响版本范围计算正确的修复版本(这是 Node 版本开发过程中的一个真实 bug, 在这里作为修复和回归测试被移植过来 —— 参见 `internal/trace/resolve.go` 中 `minimumFixedVersion` 的文档注释),合并 OSV.dev 有时为同一个 CVE 索引出的重复记录(另一个真实 bug,这次是在构建*此*移植版时发现的 —— 参见 `dedupeByCVE` 的文档注释),将每个修复的 semver 距离 (`updateImpact`) 和由此产生的单一可操作决策 (`remediationTier`) 进行分类,并将已知的、属于同一个包实例的每一个 CVE 聚合为一个 `recommendedVersion`。 3. **使用情况检测** (`internal/trace/usage.go`) — 对目标目录的 自有源文件(而非 manifest)进行第二次单独的遍历,通过正则表达式扫描该包的 import/require 语句,为每个发现的 `codeReference` 打标签。 4. **覆盖代码片段** (`internal/trace/override.go`) — 针对已知目标版本且确定为传递性的发现,生成确切的 npm/Gradle/(面向未来的 Maven) 代码片段,强制使用该版本,而无需等待父级更新。 5. **优先级** (`internal/trace/priority.go`) — 将严重程度、`usageContext` 和 `codeReference`(外加倾向于轻松获胜的小幅 `updateImpact` 权重)组合成一个 可排序的 `priorityScore` 以及 P1-P4 的 `priorityLabel`,并据此重新对每个发现进行排序 —— 这是 pipeline 的最终排序,特意采用了与 `severity` 不同的措辞,例如 "severity: CRITICAL, priority: P4"(位于仅在 dev 中的代码中的严重 CVE,在你自己的源码中从未被引用)读起来是一个合理的分类决策,而不是 矛盾。 6. **忽略过滤** (`internal/trace/ignore.go`) — 在报告之前,将 `.cvetraceignore` 与 `--ignore` 值合并,并将发现拆分为保留/忽略。 7. **报告** (`internal/report`) — 彩色终端报告,或 `--json`。 ## 个发现的字段 | 字段 | 取值 | 含义 | |---|---|---| | `dependencyScope` | `direct` / `transitive` / `unknown` | 易受攻击的包是直接在你的 manifest 中声明的,还是由你依赖的其他内容引入的。 | | `dependencyPath` | 数组或省略 | 对于**在 Node 或 Gradle 中**的传递性发现(这是此移植版解析真实依赖图的唯一生态系统):从直接依赖到这个包的链条,例如 `["webpack", "loader-utils", "vulnerable-pkg"]`。对于直接依赖省略,对于 Maven/Python 始终省略,因为这些根本没有进行传递性解析。 | | `usageContext` | `production` / `development` / `unknown` | 该包是可以从你的生产依赖项中到达,还是仅能从绝不会发布的开发/测试/构建工具(`devDependencies`,Maven `test` 作用域,Gradle `testImplementation` 等)中到达。 | | `codeReference` | `found` / `not-found` / `unknown` | 该包是否确实在你自己的源文件中的任何地方被 import/require,而不仅仅是在 manifest 中声明。**这是一个使用信号,而非可达性分析** —— `found` 并不意味着调用了特定的易受攻击的函数,而 `not-found` 也不能证明代码未被使用(忽略了动态 require、反射等)。Java/Kotlin 检测假设库的 import 与其 Maven/Gradle groupId 相匹配(通常如此,但不保证);Python 检测直接使用 PyPI 包名,这会遗漏那些 import 名称与发布名称不同的包(例如 PyYAML 是 `import yaml`)—— 这是一个已知差距,并未默默处理。请参阅 `internal/trace/usage.go` 中的 `DetectCodeReferences`。 | | `updateImpact` | `patch` / `minor` / `major` / `unknown` | 修复所需的 semver 跳跃幅度有多大 —— 这是判断向后兼容可能性的启发式方法,而非保证。Log4Shell 自身的修复(2.14.1 → 2.15.0)按此衡量本身就是一个 "minor" 级别的升级。 | | `recommendedVersion` | 版本或省略 | 在该确切包实例已知的每一个 CVE 中最高的单一修复版本 —— "升级到 X,清除一切",而不是去调和 N 个独立的逐 CVE 目标。如果目前没有任何已知修复,则省略。 | | `advisoryDetails` | 文本或省略 | OSV.dev 的完整公告文本,除了“升级”之外,通常还包含缓解/变通方法部分(例如,Log4Shell 针对无法立即升级的用户的配置标志变通方法)。 | | `remediationTier` | `safe-to-update` / `needs-approval` / `no-fix-available` / `unknown-impact` | 将 `fixedVersion` + `updateImpact` 融合为一个可直接据此分支处理的决策 —— 关于每个值能保证和不能保证什么,请参阅 `internal/trace/resolve.go` 中 `classifyRemediationTier` 的文档注释。显示为终端报告中的 `->` 动作行。 | | `overrideSnippet` | 对象或省略(仅限 JSON 输出) | 对于具有已知目标版本的传递性发现:无需等待父依赖更新即可强制锁定修补版本的精确 `{file, instructions, snippet}` —— npm `overrides`,Gradle `resolutionStrategy.force`,或(已为 Maven 支持发展到该程度做好准备)Maven `dependencyManagement`。通常是对传递性 CVE 最快的真正修复方法。对于直接依赖以及完全不进行传递性解析的生态系统会被省略。参见 `internal/trace/override.go` 中的 `generateOverrideSnippet`。 | | `priorityScore` / `priorityLabel` | 数字 / `P1`-`P4` | 结合了严重程度、`usageContext`、`codeReference` 以及小幅 `updateImpact` 加分的单一可排序的分类排名 —— 关于确切的公式,以及更重要的是它*不是*什么(它不是权威的风险评分),请参见 `internal/trace/priority.go` 中 `ComputePriority` 的文档注释。这是终端报告的实际排序顺序,也是每行上的 `[P#]` 前缀。 | Node 版本的“补救智能”集中的每个字段现在都存在于这个 移植版中 —— 请参阅[目前已实现的功能](#whats-implemented-so-far)以获取完整的 逐生态系统图景。Node 版本 (`jjuhric/cvetrace`) 仍然是 确切行为的参考;本项目致力于将其忠实地移植到 Go,而不是 重新发明它。 ## 目前已实现的功能 | 生态系统 | 读取的 Manifests | 状态 | |---|---|---| | Node.js | `package.json`, `package-lock.json` | 完全解析,包括传递依赖 —— `dependencyScope`/`usageContext`/`dependencyPath` 来自对 lockfile 自带的逐包 `dependencies` 图进行的广度优先遍历,以根 manifest 的 `dependencies`/`devDependencies` 作为种子(参见 `node.go` 中的 `buildNodeScopeMap`)。 | | Java (Maven) | `pom.xml`, 包含 `${property}` 解析 | 仅追踪直接声明的依赖项 —— 没有传递解析(这需要调用 `mvn`,目前未实现),因此 `dependencyScope` 始终为 `direct`,并且始终省略 `dependencyPath`。`usageContext` 来自映射到 `development` 的 `test`,其他所有情况都映射到 `production`。 | | Java (Gradle) | `build.gradle`/`.kts` | **完全解析**,通过使用生成的 init script 实际调用目标项目自己的 `gradlew`/`gradlew.bat` wrapper(如果不存在 wrapper,则回退到系统级的 `gradle`),包括传递依赖和通往每一个依赖的完整依赖路径 —— 准确度与 Node/npm 相同。如果无法调用 Gradle 本身(例如未安装 Java),则回退到对 `build.gradle` 进行基于正则表达式的静态解析(仅限直接依赖,无路径)。 | | Python | `Pipfile.lock`, `requirements.txt`, `pyproject.toml` (尽力而为,不是完整的 TOML 解析器 —— 参见 `python.go`) | 没有传递解析 —— 始终省略 `dependencyPath`。对于 requirements.txt/pyproject.toml,`dependencyScope` 为 `direct`,对于 Pipfile.lock 则为 `unknown`(其 lock 格式不保留最初是声明还是通过传递方式引入的条目)。`usageContext` 来自 Pipfile.lock 的 default/develop 划分、类似 `requirements-dev.txt` 的同级文件名,或者符合类似 dev 命名约定(`dev`, `test`, `docs`, `lint`, `typing`)的 pyproject.toml/Poetry 组名。 | ## 开发 ``` go build ./... # compile everything go vet ./... # catch common mistakes static analysis can find gofmt -l . # list any files that aren't formatted correctly (gofmt -w . to fix) go test ./... # run all tests ``` 为另一个操作系统交叉编译只需一个标志,无需额外工具链: ``` GOOS=windows GOARCH=amd64 go build -o cvetrace.exe ./cmd/cvetrace GOOS=darwin GOARCH=arm64 go build -o cvetrace-mac ./cmd/cvetrace GOOS=linux GOARCH=amd64 go build -o cvetrace-linux ./cmd/cvetrace ``` 测试 fixtures 位于 `test/fixtures/*-fixture-project` 之下 —— 是 Node 代码库中同名 fixtures 的副本(`minimist@0.0.8`, `log4j-core@2.14.1` 即 Log4Shell(同时位于 `java-fixture-project` 的 `pom.xml` 中,以及单独位于 `gradle-fixture-project` 的 `build.gradle` 中),以及 `PyYAML==5.3`,每一个都是真实的、已知的 CVE),保持同步,以便两个项目都能针对完全相同的已知漏洞证明其正确性。Gradle fixture 包含一个真实的、已提交的 Gradle wrapper,因此其测试会执行实际的 Gradle 调用,而不仅仅是静态解析的备用方案 —— 预计该特定测试将是测试套件中 最慢的(Gradle daemon/依赖缓存启动)。 ### 发布版本 推送一个匹配 `v*.*.*` 的标签,然后 [`.github/workflows/release.yml`](.github/workflows/release.yml) 将接管后续工作 —— 作为关卡运行完整的测试套件,为每个支持的 操作系统/架构进行交叉编译,并将该标签植入作为 `cvetrace --version` 的输出, 并将二进制文件连同 `checksums.txt` 发布到该标签的 GitHub Release: ``` git tag v1.2.3 git push origin v1.2.3 ``` ## 项目布局 ``` cvetrace-go/ go.mod # module definition (like package.json, but much smaller) cmd/cvetrace/main.go # thin executable entrypoint internal/ cli/ # argument parsing, subcommand dispatch discover/ # finds dependencies in a project directory trace/ # queries OSV.dev, resolves the correct fix version report/ # terminal + JSON output test/fixtures/ # known-vulnerable sample projects used by tests ``` 请参阅 [GO_PRIMER.md](GO_PRIMER.md) 了解为什么要这样组织。 ## License MIT
标签:Claude, CVE检测, EVTX分析, Go, Homebrew安装, Ruby工具, 日志审计