Skip to content

Commit 8813ecf

Browse files
security: add gitleaks allowlist — greens the now-real secret gate on main (#512)
**Follow-up to #511 — `main` is currently red because of it.** #511 merged at its first commit, so the caller fix landed **without** this allowlist. `main`'s Secret Scanner therefore went straight from `startup_failure` (dead — zero jobs, since 2026-07-06) to `failure` (real gate, 15 false positives). This completes the change. Net effect of #511 + this PR: **the scanner goes from not executing at all, to executing and passing honestly.** ## The 15 findings — all verified false positives | Where | Value | Why benign | |---|---|---| | `deno.lock` ×8 | hex digests | SHA-256 **integrity hashes** of public registry modules | | `lol/analysis/Project.toml` | UUID | Julia General-registry **package UUID** | | `docs/audits/otpiser-…a2ml` | 40-hex | **OpenPGP fingerprint** — published by design | | `rhodium…/haskell-registry-design.md` | `Ed25519KeyPair` | a Haskell **type name**, not a value | | `avow-lib/…/example.zig` | `abc123def456...` | commented `// Would be real token` | | `telegram-bot/NEXT-STEPS.md` ×2 | `1234567890_abc123...` | inside a fenced ASCII terminal mockup | | `…/examples.ndjson` | `0a1b2c3d4e5f…` | ascending-nibble filler walk | This wall of noise is almost certainly what the original `continue-on-error: true` was papering over — turn the gate on with no allowlist, get 15 reds that are entirely noise, someone reverts, and the estate is back to a scanner that cannot fail. **So this allowlist is the unblocker for re-pinning callers estate-wide, not merely local cleanup.** ## Design rule (documented in the file — please keep it) Allowlist by **pattern class, anchored to the whole value**. Never allowlist a path because "that file is fine", never disable a rule. `paths` is confined to formats that cannot hold a live credential (lockfile integrity hashes, dependency UUIDs). Every regex is anchored `^...$` deliberately. An earlier draft used unanchored `(?i)test|example` and **canary-testing caught that it silently allowlisted the real AWS key** `wJalrXUtnFEMI/K7MDENG/bPxRfiCY…` purely because "EXAMPLE" appears inside it. Anchoring fixed it. ## Verification against `origin/main` ``` gitleaks detect --exit-code 1 -> no leaks found (exit 0) ``` with planted realistic secrets **still detected**: `github-pat`, `gitlab-pat`, `slack-bot-token`, `stripe-access-token`, `generic-api-key` (AWS). Also clears **all 8** of `boj-server-cartridges`' equivalent fixture findings with **no repo-specific additions** (see boj-server-cartridges#99) — the evidence it works as the estate baseline template, not a standards-only patch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent dbfd661 commit 8813ecf

1 file changed

Lines changed: 105 additions & 0 deletions

File tree

.gitleaks.toml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
#
3+
# Gitleaks configuration — estate baseline.
4+
#
5+
# WHY THIS FILE EXISTS
6+
# --------------------
7+
# Until #500 the estate's gitleaks step carried `continue-on-error: true`, so
8+
# the primary secret scanner could not fail a PR. #500 made it gate for real
9+
# (`gitleaks detect --exit-code 1` against a checksum-verified binary). The
10+
# first honest run then surfaced 15 findings in this repo — every one a false
11+
# positive: module integrity hashes, package UUIDs, a public GPG fingerprint,
12+
# a Haskell type *name*, and synthetic `.example` fixtures.
13+
#
14+
# That is very likely the trap the `continue-on-error` was papering over: turn
15+
# the gate on with no allowlist, get a wall of red that is entirely noise,
16+
# someone reverts, and the estate is back to a scanner that cannot fail. This
17+
# config is therefore the *unblocker* for re-pinning callers onto the real
18+
# gate across the estate.
19+
#
20+
# DESIGN RULE — KEEP THIS WHEN EDITING
21+
# ------------------------------------
22+
# Allowlist by PATTERN CLASS, anchored to the WHOLE value. Never allowlist a
23+
# path wholesale because "that file is fine", and never disable a rule.
24+
#
25+
# Every `regexes` entry below is anchored `^...$` on purpose. An unanchored
26+
# entry is a substring match, which silently blinds the scanner: a pattern
27+
# like `(?i)test|example` would allowlist the real AWS key
28+
# `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` purely because "EXAMPLE" appears
29+
# inside it. That was caught by canary-testing an earlier draft of this file —
30+
# if you add a pattern here, plant a realistic secret and confirm it is still
31+
# detected before you commit.
32+
#
33+
# `paths` entries are limited to formats that cannot legitimately hold a live
34+
# credential (lockfile integrity hashes, dependency UUIDs). If you want to
35+
# allowlist any other path wholesale, that is a signal the secret should be
36+
# removed from the tree instead.
37+
38+
[extend]
39+
useDefault = true
40+
41+
[allowlist]
42+
description = "Estate baseline: dependency metadata, anchored placeholder shapes, published test vectors"
43+
44+
paths = [
45+
# Lockfiles: the "secrets" are SHA-256 *integrity hashes* of public registry
46+
# modules. A lockfile has no legitimate reason to contain a credential.
47+
'''(^|/)deno\.lock$''',
48+
'''(^|/)package-lock\.json$''',
49+
'''(^|/)yarn\.lock$''',
50+
'''(^|/)Cargo\.lock$''',
51+
# Julia project manifests: entries are public package UUIDs from the General
52+
# registry, which trip the generic-api-key entropy heuristic.
53+
'''(^|/)Project\.toml$''',
54+
'''(^|/)Manifest\.toml$''',
55+
]
56+
57+
regexes = [
58+
# --- documented placeholder shapes (all anchored to the whole value) ------
59+
60+
# Ellipsis-terminated illustrative values: `abc123def456...` (carrying an
61+
# explicit "// Would be real token" comment), `1234567890_abc123...` inside a
62+
# fenced ASCII mockup of terminal output. A real credential is never elided.
63+
'''^[A-Za-z0-9_\-]{4,}\.\.\.$''',
64+
65+
# Repeated-character masks: a provider prefix followed by a long run of a
66+
# single filler character (the GitLab, GitHub and OpenAI token prefixes are
67+
# all commonly masked this way in docs). A real credential never repeats one
68+
# character 8+ times.
69+
#
70+
# Deliberately described rather than shown: writing the masked token out
71+
# literally here makes THIS file trip the estate's Hypatia secret detector
72+
# ("Secret found: GitLab PAT"), which is how that was found. Do not add
73+
# literal example tokens to this file, even inside comments.
74+
'''^[A-Za-z0-9_\-]*?(x{8,}|X{8,}|y{8,}|z{8,}|0{12,}|\*{6,})[A-Za-z0-9_\-]*$''',
75+
76+
# Monotonic hex walks used as filler: `0123456789abcdef` repeated,
77+
# `ABCDEF0123456789ABCDEF0123456789`, `0a1b2c3d4e5f6a7b…` (ascending nibble
78+
# pairs). These are keyboard-walk fillers, never generated key material.
79+
'''^(?i:0123456789abcdef|abcdef0123456789|0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1|deadbeef|cafebabe)+$''',
80+
81+
# Snake-case self-labelled fixtures, e.g. `sg_test_api_key_abc123`. Anchored
82+
# and shape-constrained: an all-lowercase snake_case string with an explicit
83+
# test/demo marker as a bounded segment. Real API keys carry mixed case,
84+
# digits and symbols and will not match this.
85+
'''^[a-z]{2,6}_(test|demo|fake|sample|example|dummy|placeholder)_[a-z0-9_]{2,40}$''',
86+
87+
# --- published test vectors ----------------------------------------------
88+
# RFC 8032 §7.1 TEST 1 Ed25519 seed and the public key it derives. These
89+
# exact values are printed in the RFC; checking that one yields the other is
90+
# the entire point of the interoperability test.
91+
'''^9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60$''',
92+
'''^d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a$''',
93+
94+
# --- public-by-definition / non-secret identifiers ------------------------
95+
# OpenPGP 40-hex fingerprints. A fingerprint *identifies* a key; it is
96+
# published deliberately (SECURITY.md, audit records, keyservers) and
97+
# discloses nothing. Secret key *material* is PEM-armoured and is caught by
98+
# the dedicated private-key rules, which are NOT allowlisted here.
99+
'''^[A-F0-9]{40}$''',
100+
101+
# Cryptographic *type names* appearing in source/design docs, e.g. the
102+
# Haskell declaration `data Ed25519KeyPair = Ed25519KeyPair`. A bare
103+
# CamelCase identifier with no digits or separators is a type, not a key.
104+
'''^(Ed25519|X25519|Curve25519|RSA|ECDSA|EdDSA|Secp256k1)[A-Za-z]*$''',
105+
]

0 commit comments

Comments
 (0)