gitleaks 实战:在 CI 中自动拦截密钥泄露

每次把代码推上仓库,都可能顺手把数据库密码、云厂商 AK/SK、第三方 API Token 一起提交了上去。密钥泄露是开发者最容易踩、代价也最高的坑之一:一旦明文令牌进了 Git 历史,哪怕立刻删掉,也已经留在了公开镜像和攻击者的爬虫里。gitleaks 是一款开源的密钥扫描工具,能在你提交前和 CI 流水线里自动识别出这些硬编码的敏感字符串。本文从本地扫描讲到 pre-commit 钩子,再到 GitHub Actions 全程接入,帮你把密钥泄露挡在合并之前。

为什么硬编码密钥这么危险

很多团队以为“删掉那次提交就好了”,但现实是:GitHub、Gitee 的公开仓库会被各种爬虫实时镜像,内部私有仓库也常有离职成员、第三方集成、CI 日志把历史暴露出去。攻击者手里有一整套针对令牌格式的自动化扫描脚本,能在几分钟内把你刚 push 的 AK/SK 变成他们云账单上的算力。更麻烦的是,密钥一旦进过历史,彻底清除需要重写整个 Git 历史(git filter-repo),成本远高于一开始就拦住。所以业内的共识是“安全左移”——在提交和合并这两个最低成本的节点直接拦截。

gitleaks 是什么

gitleaks 用 Go 写成,核心逻辑是“正则匹配 + 熵检测(entropy)”:正则负责识别有固定形态的密钥(比如 AWS 的 AKIA[0-9A-Z]{16}),熵检测负责抓“看起来像随机长串”的高熵字符串(比如 32 位以上的 hex)。它内置了 100 多条规则,覆盖 AWS、GCP、GitHub、GitLab、Slack、Stripe 等主流服务商,也支持用一份 .gitleaks.toml 定义你自己的密钥格式。单文件二进制、无外部依赖,本地、容器、CI 里都能直接跑。

本地安装与第一次扫描

安装

# macOS / Linux (Homebrew)
brew install gitleaks

# 或用 Go 直接装
go install github.com/gitleaks/gitleaks/v8@latest

# 或从 release 下载单文件二进制
# https://github.com/gitleaks/gitleaks/releases
gitleaks version

扫描整个仓库(含历史)

detect 是默认子命令,会扫描工作区未提交改动 + 全部 Git 历史,发现命中就返回非零退出码(方便接 CI)。

# 扫描当前仓库,结果导出成 JSON 报告
gitleaks detect --source . --report gitleaks-report.json --verbose

# 只想看,不写报告
gitleaks detect --source . --verbose

只扫未提交改动(pre-commit 用)

提交前钩子不需要扫历史,只需扫已暂存(staged)的内容。早期版本用 gitleaks protect,新版本统一为 gitleaks detect 配合暂存区参数;下面用官方 pre-commit 钩子,它内部帮你处理好只扫本次改动。

读懂扫描报告

扫描发现的每条命中,报告里都会带这些字段,定位问题时直接看就行:

命中后先别慌:报告里同时会给出 RuleID 和行号,你就能判断是真密钥还是测试占位符、文档示例。误报就用下一节的基线或 allowlist 处理。

接进 pre-commit 钩子

最省事的本地拦截方式,是配合 pre-commit 框架 在每次 git commit 自动跑扫描。--redact 会把输出里的密钥脱敏,避免泄露到终端日志。

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.4
    hooks:
      - id: gitleaks
        args: ["--verbose", "--redact"]

装好框架后执行一次 pre-commit install,之后任何一次提交只要命中规则就会直接被拦下,开发者在本地就能改,不用等到 CI 才被发现。

接进 GitHub Actions CI

pre-commit 只能拦“装了钩子的人”,CI 才是团队级的硬闸门。下面这段 Workflow 在每次 push 和 PR 时跑官方 gitleaks-action;fetch-depth: 0 是为了拉全历史,避免漏掉旧提交里的密钥。

# .github/workflows/secret-scan.yml
name: secret-scan
on: [push, pull_request]

jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Run gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITLEAKS_ENABLE_UPLOAD_ARTIFACT: "true"

接入后,PR 里只要出现疑似密钥,流水线直接标红,合并被挡住。结合你已有的 GitHub Actions CI/CD 体系,这一步几乎是零成本的安全增量。

自定义规则:识别你自己的密钥格式

通用规则认不出你们公司的内部令牌。在仓库根放一份 .gitleaks.toml,用 rules 扩展即可。keywords 是性能开关——gitleaks 先用关键词快速过滤文件,命中才跑正则,能大幅提速。

# .gitleaks.toml
title = "gitleaks config extended"

[[rules]]
id = "my-api-token"
description = "Detect MyCompany internal API token"
regex = '''MYTOKEN-[0-9a-zA-Z]{32}'''
secretGroup = 1
keywords = ["MYTOKEN"]

这里的 secretGroup = 1 表示只把正则里第一个捕获组(即 MYTOKEN- 后面的部分)当作密钥上报,避免把前缀也写进报告。

误报与基线管理

老仓库一上来扫可能一堆误报。先生成一份基线,把存量问题“冻结”,之后只拦新引入的密钥:

# 生成基线(把当前命中记录下来)
gitleaks detect --report .gitleaks-baseline.json

# 之后扫描时忽略基线里的存量问题
gitleaks detect --baseline-path .gitleaks-baseline.json --report new.json

# 单条误报也可在文件里用注释放行:
# gitleaks:allow

基线要提交进仓库,团队共用;等存量密钥逐步轮转清理后,再把基线清空、回归“零容忍”。

把密钥扫描做成团队规范

工具只是第一步,关键是写进流程。在 技术方案评审 阶段就把“密钥不进库”列为硬性检查项;把它和 技术债务管理 挂钩,存量密钥泄露风险就是一项要清零的债务;再配合 Git 分支模型 的保护分支规则,PR 不通过扫描就不准合。三道防线(本地钩子 + CI 闸门 + 评审规范)叠加,密钥泄露基本能被堵死。

小结

gitleaks 上手成本极低:本地一条 gitleaks detect 就能扫,pre-commit 钩子拦提交,GitHub Actions 卡合并,自定义规则覆盖内部令牌,基线管理处理存量误报。密钥一旦泄露,补救成本是指数级的,而拦在提交和合并这两个节点,几乎不花什么力气。现在就把这几段配置粘进你的仓库,比哪天半夜收到云厂商的异常账单再去救火,划算得多。

上一篇 Zellij 实战:比 tmux 更现代的开源终端复用器
字段含义
RuleID命中的规则名,如 aws-access-tokengithub-pat
File出现密钥的文件路径
StartLine / EndLine密钥所在的起止行号
Commit所在提交的哈希(扫历史时才有)
Entropy该字符串的香农熵,越高越像随机密钥