|
| 1 | +# Betterleaks 使用指南 |
| 2 | + |
| 3 | +## 一、它是什么 |
| 4 | + |
| 5 | +**Betterleaks** 是 Gitleaks 原作者团队推出的**新一代密钥扫描器**,用于在代码、Git 仓库、文件系统中检测被硬编码的密码、API Key、Token 等机密信息。由 Aikido Security 赞助开发。 |
| 6 | + |
| 7 | +相比 Gitleaks 主要的进化点: |
| 8 | + |
| 9 | +| 特性 | 说明 | |
| 10 | +|------|------| |
| 11 | +| **CEL 过滤** | 用 [CEL(Common Expression Language)](https://cel.dev) 写规则过滤,可访问 git author、commit message、文件路径等上下文 —— 比 Gitleaks 的 `[[allowlist]]` 表达力强得多 | |
| 12 | +| **密钥有效性验证** | 在规则里直接发 HTTP 请求,验证扫到的密钥是不是**真的活跃可用** | |
| 13 | +| **Token 效率过滤** | 用 BPE 分词判断字符串「像不像人类自然语言」,降低自然语言导致的误报 | |
| 14 | +| **极速扫描** | 默认并行化 + Aho-Corasick 关键字预过滤 + RE2 | |
| 15 | +| **轻量便携** | 单个小体积二进制,可嵌入任何系统 | |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +## 二、安装 |
| 20 | + |
| 21 | +按你的环境选一种即可: |
| 22 | + |
| 23 | +```bash |
| 24 | +# macOS / Linux(Homebrew) |
| 25 | +brew install betterleaks |
| 26 | +# 或 |
| 27 | +brew install betterleaks/tap/betterleaks |
| 28 | + |
| 29 | +# Fedora |
| 30 | +sudo dnf install betterleaks |
| 31 | + |
| 32 | +# Docker |
| 33 | +docker pull ghcr.io/betterleaks/betterleaks:latest |
| 34 | + |
| 35 | +# 源码构建(需要 Go) |
| 36 | +git clone https://github.com/betterleaks/betterleaks |
| 37 | +cd betterleaks |
| 38 | +make build |
| 39 | +``` |
| 40 | + |
| 41 | +--- |
| 42 | + |
| 43 | +## 三、基本用法(5 种扫描模式) |
| 44 | + |
| 45 | +### 1. 扫描 Git 仓库(最常用) |
| 46 | +```bash |
| 47 | +betterleaks git /path/to/repo -v --git-workers=16 |
| 48 | +``` |
| 49 | +- `-v`:输出详细信息(发现的每条 finding) |
| 50 | +- `--git-workers=16`:并行 worker 数,按 CPU 核数调 |
| 51 | + |
| 52 | +### 2. 扫描本地目录或单个文件 |
| 53 | +```bash |
| 54 | +betterleaks dir /path/to/file/or/dir -v |
| 55 | +``` |
| 56 | +适合扫描非 Git 项目、构建产物、配置目录等。 |
| 57 | + |
| 58 | +### 3. 扫描 GitHub 组织 / 用户 / 资源 |
| 59 | +```bash |
| 60 | +# 扫整个组织 |
| 61 | +betterleaks github https://github.com/betterleaks |
| 62 | + |
| 63 | +# 扫某用户,并包含 issue / PR / actions / release / gist |
| 64 | +betterleaks github https://github.com/cooluser123456789 \ |
| 65 | + --include issues,prs,actions,releases,gists |
| 66 | + |
| 67 | +# 扫某个具体 PR(只扫评论,不扫描描述) |
| 68 | +betterleaks github https://github.com/betterleaks/betterleaks/pull/113 |
| 69 | +``` |
| 70 | +> 扫 GitHub 需要 Token,通常通过环境变量 `GITHUB_TOKEN` 提供。 |
| 71 | +
|
| 72 | +### 4. 扫描标准输入(pipeline 友好) |
| 73 | +```bash |
| 74 | +cat some_file.txt | betterleaks stdin -v |
| 75 | +``` |
| 76 | +适合 CI 中扫单个文件、日志、临时片段。 |
| 77 | + |
| 78 | +### 5. 退出码 |
| 79 | +``` |
| 80 | +0 - 没有泄漏 |
| 81 | +1 - 发现泄漏或出错 |
| 82 | +126 - 未知 flag |
| 83 | +``` |
| 84 | +可用 `--exit-code` 自定义。CI 流水线一般直接靠这个非 0 退出码来阻断合并。 |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +## 四、配置文件 `.betterleaks.toml` |
| 89 | + |
| 90 | +Betterleaks 的精髓在于配置 —— **过滤和验证都是用 CEL 写的代码逻辑**,不是简单的正则白名单。 |
| 91 | + |
| 92 | +> 建议先花 30 分钟看一遍 [CEL 语言](https://cel.dev) 再写过滤规则。 |
| 93 | +
|
| 94 | +把以下文件放到仓库根目录命名为 `.betterleaks.toml`: |
| 95 | + |
| 96 | +```toml |
| 97 | +# 全局 prefilter:在做正则匹配之前先跑,可访问 attributes |
| 98 | +# 用于快速排除明显不需要扫的资源(图片、node_modules、机器人提交等) |
| 99 | +prefilter = ''' |
| 100 | +(matchesAny(attributes[?"path"].orValue(""), [ |
| 101 | + r"""(?i)\.(?:bmp|gif|jpe?g|png|svg|tiff|pdf|exe)$""", |
| 102 | + r"""(?:^|/)node_modules(?:/.*)?$""", |
| 103 | + r"""(?:^|/)vendor(?:/.*)?$""" |
| 104 | +])) |
| 105 | +|| attributes[?"git.author_name"].orValue("") == "renovate[bot]" |
| 106 | +''' |
| 107 | + |
| 108 | +# 全局 filter:对每一条 candidate secret 跑一次 |
| 109 | +# 命中此条件的 finding 会被丢弃(视为误报) |
| 110 | +filter = ''' |
| 111 | +containsAny(finding["secret"], [ |
| 112 | + "EXAMPLE", |
| 113 | + "CHANGEME", |
| 114 | + "YOUR_API_KEY_HERE", |
| 115 | + "0000000000000000" |
| 116 | +]) |
| 117 | +''' |
| 118 | + |
| 119 | +# === 规则定义 === |
| 120 | +[[rules]] |
| 121 | +id = "github-fine-grained-pat" |
| 122 | +description = "GitHub Fine-Grained PAT,可能导致仓库未授权访问" |
| 123 | +regex = '''github_pat_\w{82}''' |
| 124 | +keywords = ["github_pat_"] |
| 125 | + |
| 126 | +# 规则级 filter:只对当前规则生效 |
| 127 | +filter = ''' |
| 128 | +( |
| 129 | + attributes[?"git.author_name"].orValue("") == "ci-runner" && |
| 130 | + attributes[?"path"].orValue("").startsWith("mocks/") && |
| 131 | + finding["secret"].contains("TESTING") |
| 132 | +) |
| 133 | +|| (entropy(finding["secret"]) <= 3.0) |
| 134 | +''' |
| 135 | + |
| 136 | +# 异步验证:发请求看密钥是否真的有效 |
| 137 | +validate = ''' |
| 138 | +cel.bind(r, |
| 139 | + http.get("https://api.github.com/user", { |
| 140 | + "Accept": "application/vnd.github+json", |
| 141 | + "Authorization": "token " + secret |
| 142 | + }), |
| 143 | + r.status == 200 && r.json.?login.orValue("") != "" ? { |
| 144 | + "result": "valid", |
| 145 | + "username": r.json.?login.orValue(""), |
| 146 | + "name": r.json.?name.orValue(""), |
| 147 | + "scopes": r.headers[?"x-oauth-scopes"].orValue("") |
| 148 | + } : r.status in [401, 403] ? { |
| 149 | + "result": "invalid", |
| 150 | + "reason": "Unauthorized" |
| 151 | + } : unknown(r) |
| 152 | +) |
| 153 | +''' |
| 154 | +``` |
| 155 | + |
| 156 | +**几个关键概念,务必区分清楚**: |
| 157 | + |
| 158 | +| 项 | 执行时机 | 可访问数据 | 用途 | |
| 159 | +|---|---|---|---| |
| 160 | +| `prefilter` | 正则匹配**之前** | 只能访问 `attributes`(路径、作者、commit msg 等) | 快速跳过无需扫的资源,省 CPU | |
| 161 | +| `filter` | 正则匹配**之后** | `attributes` + `finding`(如 `finding["secret"]`、`finding["match"]`) | 排除误报 | |
| 162 | +| `validate` | filter 通过**之后** | finding 数据 + `http.*` 等 | 异步发 HTTP 验证密钥是否真有效 | |
| 163 | + |
| 164 | +完整默认规则可参考官方仓库 [`config/betterleaks.toml`](https://github.com/betterleaks/betterleaks/blob/main/config/betterleaks.toml),详细配置文档见 [`docs/config.md`](https://github.com/betterleaks/betterleaks/blob/main/docs/config.md)。 |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +## 五、典型 CI 集成(GitHub Actions 示例) |
| 169 | + |
| 170 | +```yaml |
| 171 | +name: secret-scan |
| 172 | +on: [push, pull_request] |
| 173 | + |
| 174 | +jobs: |
| 175 | + betterleaks: |
| 176 | + runs-on: ubuntu-latest |
| 177 | + steps: |
| 178 | + - uses: actions/checkout@v4 |
| 179 | + with: |
| 180 | + fetch-depth: 0 # 扫全部历史需要完整 git 历史 |
| 181 | + - name: Run Betterleaks |
| 182 | + run: | |
| 183 | + docker run --rm -v $PWD:/repo ghcr.io/betterleaks/betterleaks:latest \ |
| 184 | + git /repo -v --exit-code=1 |
| 185 | +``` |
| 186 | +扫到任何有效泄漏即 `exit 1`,PR 会被自动标红、阻断合并。 |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +## 六、忽略文件 `.betterleaksignore` |
| 191 | + |
| 192 | +仓库根目录放一个 `.betterleaksignore`,写入要忽略的 **finding 指纹**(每行一个),即可永久豁免已审阅过的误报。指纹会在 `-v` 输出里给出。 |
| 193 | + |
| 194 | +--- |
| 195 | + |
| 196 | +## 七、建议的上手路线 |
| 197 | + |
| 198 | +1. `brew install betterleaks`(或 docker pull) |
| 199 | +2. 进项目目录 `betterleaks git . -v` 跑一次,先看默认规则能扫出什么 |
| 200 | +3. 把误报加入 `.betterleaksignore`,或在 `.betterleaks.toml` 里用 `filter` 表达式批量处理 |
| 201 | +4. 给最关心的密钥类型(GitHub PAT、AWS、自家内部 token)加上 `validate` 主动验活 |
| 202 | +5. 在 CI 中以 `--exit-code=1` 接入流水线,做合并前阻断 |
| 203 | + |
| 204 | +--- |
| 205 | + |
| 206 | +参考来源: |
| 207 | + |
| 208 | +[^1]: [Betterleaks 官方 GitHub 仓库 README](https://github.com/betterleaks/betterleaks) |
| 209 | +[^2]: [Regex is (almost) all you need —— 检测引擎原理博客](https://lookingatcomputer.substack.com/p/regex-is-almost-all-you-need) |
| 210 | +[^3]: [CEL 表达式语言官网](https://cel.dev) |
| 211 | + |
0 commit comments