Skip to content

Commit 1b8cd38

Browse files
hyperpolymathclaude
andcommitted
security: re-pin secret scanner onto the real gitleaks gate
The caller pinned standards' secret-scanner-reusable at @d135b05 (2026-06-24), which is 64 commits behind standards#500. At that pin the gitleaks step carries `continue-on-error: true` — the scan runs, reports success, and cannot fail. This repo's Secret Scanner has therefore been green while being incapable of failing. Re-pinned to @c65436ee, which contains #500: gitleaks now runs as a pinned, checksum-verified binary with `--exit-code 1` and gates for real. Also narrowed the caller's grant to `contents: read`. The new reusable invokes a binary rather than gitleaks-action, so it no longer requests `pull-requests: write` / `actions: read`. Added the estate baseline .gitleaks.toml (byte-identical to the one in standards), without which the now-real gate reds on false positives — dependency-metadata hashes, documented placeholder shapes and published test vectors. It allowlists by anchored pattern class, never by file and never by disabling a rule, so a genuinely new secret still trips. Verified: `gitleaks detect --exit-code 1` passes on this tree after the change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1289146 commit 1b8cd38

2 files changed

Lines changed: 106 additions & 3 deletions

File tree

.github/workflows/secret-scanner.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,5 @@ jobs:
2323
scan:
2424
permissions:
2525
contents: read
26-
pull-requests: write
27-
actions: read
28-
uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@d135b05bfc647d0c0fbfedc7e80f37ea50f49236 # main as of 2026-06-01
26+
uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@c65436ee3351cd6b0fa14b142938b195efc77586 # main as of 2026-06-01
2927
secrets: inherit

.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)