首页
/ TruffleHog 静态检查工具 checksecretparts:确保 detector 结果完整填充 SecretParts

TruffleHog 静态检查工具 checksecretparts:确保 detector 结果完整填充 SecretParts

2026-09-10 16:21:11作者:余洋婵Anita

TruffleHog 的 hack/checksecretparts 是一个基于 Go AST 的静态分析小工具,专门扫描 pkg/detectors/ 下所有 detector 包,找出那些构造了 detectors.Result 复合字面量却没有填充 SecretParts 字段的代码位置。本文以该工具的设计文档为骨架,结合其源码实现(main.gocheck.gocheck_test.go)和它在 CI 中的实际用法,讲解它的检查逻辑、运行方式、警告转失败(fail)的迁移流程及其适用范围。

背景:SecretParts 在 TruffleHog 中的作用

在继续介绍这个检查工具之前,先理解它守护的字段。detectors.Result 是 TruffleHog 中每个 detector 在命中潜在凭证后产出的核心结构,定义在 pkg/detectors/detectors.go。其中 SecretParts 字段的注释明确说明了它的用途:

// SecretParts holds the individual components of a (potentially multi-part)
// credential, keyed by a component name. It is used by analyzers where
// the keys are analyzer specific and should match what the
// corresponding analyzer expects.
SecretParts map[string]string

也就是说,SecretParts 保存一个(可能由多个部分组成)凭证的各个组成部分,以组件名作为 map 的键。它主要服务于 pkg/analyzer 中的分析器(Analyzer):分析器按特定的键名从这份 map 里取回 ID、Secret、Token、SID 等部分,再进一步探测该凭证在对应服务上的权限信息。

pkg/analyzer/cli.go 可以看到分析器消费这些键的方式:SecretInfo 携带 Parts map[string]string,然后按服务类型读取不同的键。例如:

  • Twilio 读取 secretInfo.Parts["sid"]secretInfo.Parts["key"]cli.go);
  • PlanetScale 读取 Parts["id"]Parts["token"]cli.go);
  • Docker Hub 读取 Parts["username"]Parts["pat"]cli.go);
  • Datadog 读取 Parts["api_key"]Parts["app_key"]Parts["endpoint"]cli.go);
  • Jira 读取 Parts["domain"]Parts["email"]Parts["token"]cli.go)。

对应地,detector 侧需要按同样约定填充这些键。以 pkg/detectors/adobeio/adobeio.go 为例:

s1 := detectors.Result{
    DetectorType: detector_typepb.DetectorType_AdobeIO,
    Raw:          []byte(key),
    RawV2:        []byte(key + id),
    SecretParts: map[string]string{
        "key": key,
        "id":  id,
    },
}

因此,如果一个 detector 构造 detectors.Result 时遗漏了 SecretParts,即便它能正确命中密钥,后续的分析器环节也拿不到分片后的凭证组件,导致凭证分析能力静默失效。checksecretparts 正是为了在代码合并前拦截这类遗漏而存在。

checksecretparts 检查什么

根据 hack/checksecretparts/README.md,该工具的核心检查流程如下:

  1. pkg/detectors/ 下的每一个目录(递归进入子包)执行检查;
  2. 非测试.go 文件中,找出所有形如 detectors.Result{...}&detectors.Result{...} 的复合字面量(composite literal);
  3. 如果该包内任何地方都没有提及 SecretParts,则为每一处构造点各输出一条警告。

这里的判断单元是"包"而非"字面量":只要包内存在至少一处对 SecretParts 的引用,所有构造点都不会被报告;反之,只要包内完全没有提及 SecretParts,则每一处 detectors.Result{...} 构造都会被单独列出。这是一种低成本、低噪声的启发式检查——它不要求每次构造都必须带 SecretParts(因为有些 detector 可能通过辅助函数集中填充),而是要求每个包整体必须"意识到"该字段的存在。

源码级原理:基于 Go AST 的语法检查

工具由三个文件组成:

目录收集与去重(main.go)

main.gocollectPackageDirs 把用户传入的根目录展开成"排序、去重、且至少包含一个非测试 .go 文件"的目录列表。几个值得注意的实现细节:

  • 传入的根路径支持 "/..." 后缀(与 go list 对齐),但会被直接剥掉,因为工具总是递归扫描;
  • 遍历时跳过 testdatavendor 以及所有以 . 开头的目录(main.go);
  • 只有包含至少一个非 _test.go 的 Go 文件的目录才被纳入扫描(dirHasNonTestGoFile);
  • 多个根目录的扫描结果会通过 seen map 去重,避免重复扫描同一目录。

入口流程(main.go)依次为:解析 flag → 确定根目录(无参数时默认 ./pkg/detectors)→ 收集包目录 → 对每个目录执行 CheckPackageDir → 打印所有发现 → 根据模式决定退出码。

AST 匹配逻辑(check.go)

核心检查在 check.go 中,分为三步:

1. 解析目录内全部非测试文件CheckPackageDir):读取目录条目,过滤掉子目录、非 .go 文件以及 _test.go 文件,用 go/parser.ParseFile 逐个解析为 *ast.File。注意它传入 parser.SkipObjectResolution,进一步降低了开销。

2. 定位 detectors.Result 构造点findResultConstructions):用 ast.Inspect 深度遍历语法树,凡是 *ast.CompositeLit 节点,先通过 isDetectorsResultType 判断类型是否为 detectors.Result。类型判断会防御性地展开括号表达式(*ast.ParenExpr)和解引用表达式(*ast.StarExpr),最终要求是一个选择器表达式:包标识符名为 detectors、选择器名为 Resultcheck.go)。

3. 判断字面量内是否已含 SecretPartshasSecretPartsKey):遍历复合字面量的元素,只要出现 KeyValueExpr 且键是名为 SecretParts 的标识符,就认为该构造点已经填充。否则记下 { 的位置作为一处发现(check.go)。

最终,所有发现按文件名、字节偏移排序后包装为 Finding(含 PositionPackage 两个字段),交由 main 输出。

本地运行方式

工具的两种运行模式由 flag 区分,完整命令如下(来自 README.md):

# 警告模式(默认):打印发现,只要扫描过程本身没出错就总是以退出码 0 结束
go run ./hack/checksecretparts

# 指定扫描目录(默认扫描 ./pkg/detectors,这里改为只扫 aws 与 github)
go run ./hack/checksecretparts ./pkg/detectors/aws ./pkg/detectors/github

# 失败模式:只要有任何发现就以退出码 1 结束
# 适用于所有 detector 都已迁移到填充 SecretParts 之后
go run ./hack/checksecretparts -fail

两个 flag 的语义(main.go):

Flag 默认值 作用
-fail false 有发现时以退出码 1 退出,适合作为 CI 门禁
-quiet false 没有任何发现时,抑制"scanned N package(s), no findings"这行摘要

输出解读

有发现时,每个构造点输出一行定位信息(main.go):

<文件路径>:<行号>:<列号>: warning: detectors.Result constructed without SecretParts

随后在标准错误上给出汇总,例如(main.go):

checksecretparts: 5 finding(s) across 3 package(s) constructing detectors.Result without SecretParts

无发现时输出(除非加了 -quiet):

checksecretparts: scanned 42 package(s), no findings

另外需要区分两类退出码:1 表示"存在发现且开启了 -fail",2 表示扫描本身失败(例如目录不存在、路径不是目录、Go 文件解析出错),后者在 main.go 中通过 os.Exit(2) 处理。

从警告翻转成 CI 门禁

README.md 的 "Flipping warning → fail" 一节给出了把该检查升级为强制门禁的迁移步骤:

  1. .github/workflows/lint.ymlchecksecretparts job 中,去掉 continue-on-error: true,并把运行步骤改为传入 -fail
  2. 剩余的 detector 迁移工作必须在同一个 PR 内与翻转改动一起合入,避免中间态进入主干。

从当前仓库的 .github/workflows/lint.yml 可以看到该 job 的实际形态:它基于 ubuntu-latest,通过 actions/setup-go 固定 Go 1.27,运行步骤直接执行 go run ./hack/checksecretparts -fail ./pkg/detectors。这意味着本仓库已经完成了 README 所描述的迁移——检查当前已处于强制(gating)状态,任何遗漏 SecretParts 的 detector 包都会让 lint 流水线失败。

测试用例揭示的检查边界

check_test.go 用表驱动测试覆盖了检查的语义边界,这些用例本身就是理解工具行为的最佳文档:

测试场景 期望结果 说明
构造 detectors.Result 但未填 SecretParts 报告 1 条 基础命中场景
复合字面量内直接写 SecretParts: map[string]string{...} 不报告 键出现在字面量元素中即视为已填充
字面量构造后再 r.SecretParts = ... 赋值 仍报告 1 条 只检查字面量内部,不追踪后续赋值
包内根本没有 detectors.Result 构造 不报告 无构造点则无事可报
SecretParts 只出现在 _test.go 仍报告 测试文件不参与抑制判断
指针形式 &detectors.Result{...} 报告 StarExpr 会被展开后匹配
同一包内多处构造 逐处报告 每个构造点各一条 finding

最后一个需要留意的边界写在 README.md 的 "Scope limits" 一节:这是一个纯语法检查,它按选择器表达式的名字匹配 detectors.Result。如果某个包把导入重命名成了别的标识符(例如 d "...detectors" 后写 d.Result{...}),工具就无法捕获。README 同时确认:当前代码库中不存在这样的重命名,因此该限制暂不影响检查效果。同理,工具也不做跨文件的数据流分析,SecretParts 在辅助函数中集中填充的模式能否被识别,取决于该辅助函数是否与构造点处于同一个包内。

对 detector 开发的实践建议

结合本工具与 TruffleHog 的 analyzer 体系,编写或迁移 detector 时可以遵循以下做法:

  • 构造点直接填充:在 detectors.Result{...} 字面量内显式给出 SecretParts,键名与对应 analyzer 期望的键保持一致(多组件凭证尤其重要,例如 Adobe IO 的 "key" + "id");
  • 保持包内一致性:即便通过辅助函数填充,也要确保包内至少有一处引用 SecretParts,否则整个包都会触发警告;
  • 迁移与翻转同 PR:若把新增字段的迁移和 CI 翻转分开提交,中间态可能导致主干 lint 失败;
  • 利用测试验证行为TestCheckPackageDir 展示了如何用临时目录 + 内存文件构造最小复现,可在本地快速验证某个 detector 写法是否会被检查命中。

总而言之,hack/checksecretparts 是 TruffleHog 仓库中一个"小而准"的工程质量守门员:它用约两百行代码加上完善的表驱动测试,把 detectors.Result.SecretParts 的规范化填充从"靠自觉"变成了"靠检查",为 pkg/analyzer 的凭证权限分析链路提供了可靠的输入保障。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
527