Commit ee93fc8
authored
security: add gitleaks scanning + 2026-04-18 delta review (#403)
* security: add gitleaks scanning + 2026-04-18 security delta review
Delta review between PR #377 (2026-03-17) and 7b42e36 found no new
critical/high issues. Three cybersecurity-skills toolkits ran:
gitleaks secret scanning, GitHub Actions workflow hardening, and
cryptographic audit of the delta.
Historic leak surfaced: 4 real test-instance tokens (zwqg, plasma,
zulu, badger) sat in public git history from late-2025 / early-2026.
All four are now dead ('Invalid token' response), so the exposure is
stale — but worth documenting and preventing recurrence.
Adds:
- .gitleaks.toml: custom rules for NetBox v1 (40-hex) and v2 (nbt_*)
token formats plus a project-wide allowlist (docker-compose
bootstrap token, Slack webhook placeholders, generic 'your-key'
patterns)
- .gitleaks-baseline.json: snapshot of 8 historic (dead-token)
findings so CI only flags new secrets
- .github/workflows/secret-scan.yml: gitleaks CI on every push/PR
to dev/main via gitleaks-action@v2
- .pre-commit-config.yaml: opt-in local gitleaks pre-commit hook
- docs/superpowers/reviews/2026-04-18-security-delta-review.md:
full findings report with severity tags and follow-up PR list
Medium findings documented for follow-up (not fixed here):
- pre-release-validation.yml: 5 \${{ inputs.* }} interpolations
into pwsh run-blocks (script injection vector via workflow_dispatch)
- actions/* SHA pinning: 25+ unpinned references to GitHub-owned
actions (supply-chain hardening)
Positive findings worth preserving:
- PR #377 HTTP-plaintext warning, SkipCertificateCheck opt-in,
AllowInsecureRedirect warning are all intact
- zwqg token was protected by NetBox per-token IP allowlist during
its live window — a defense-in-depth layer that should be
enabled on all prod-hosted tokens
- Central auth + TLS code in InvokeNetboxRequest, Connect-NBAPI,
Set-NBCipherSSL is unchanged since PR #377 (no regressions)
* security: widen gitleaks v1-token rule to catch low-entropy hex
The initial rule required a 'netbox_token' / 'api_token' prefix, which
missed tokens committed as plain PowerShell 'Token = ''aaaa4bb9...'''.
Gitleaks' default generic-api-key uses entropy >= 3.5, but the exposed
'aaaa4bb91eb7...' token scores 3.37 (below threshold) because of the
'aaaa' prefix.
Widened the keyword alternation to also match bare 'token' / 'apikey' /
'api_key' as assignment prefixes, which catches the common PowerShell
hashtable / YAML / JSON patterns without being so loose that it matches
any 40-hex string.
Regenerated the baseline to capture the 9 historic findings (previous
8 + the 'aaaa4bb9' in ScenarioTestHelper.psm1 that was missing).
Verified:
- Synthetic test: 'Token = ''aaaa...''' now flagged
- Baseline-suppressed full scan: 'no leaks found' on HEAD
* security: address Gemini review — tighten allowlist, drop path exclusions
Two of Gemini's three review comments were valid:
1. Bracket-placeholder regex was too broad (HIGH — valid)
Previous: `(<|\[|\{)[^>\]\}]*(token|key|secret|password)[^>\]\}]*(>|\]|\})`
This silently allowlisted real secrets inside PowerShell hashtables and
JSON objects, e.g.:
@{ token = 'real_secret_value' }
{"secret": "real_value"}
The curly-brace match would swallow the entire structure.
Fix: narrow to angle-bracket placeholders only, with an enumerated
placeholder-prefix word:
<(your|placeholder|example|fake|insert|my)?[-_]?(api[-_]?)?(token|key|secret|password|pat)>
Still matches <YOUR_TOKEN>, <MY_API_KEY>, <PAT>, <TOKEN>. Does NOT
match @{ token = '...' } or {"secret": "..."} — real secrets in those
contexts are now correctly flagged.
2. Tests/ and docs/ path exclusions dropped (MEDIUM — valid)
Gemini: "Real tokens have landed in tests before" — and indeed, the
very tokens this PR documents leaked via Tests/Scenario/ScenarioTestHelper.psm1.
Keeping these paths scannable catches that class of mistake again.
Added 'nbt_abc123def456.ghijklmnopqrstuvwxyz1234567890' (the one
placeholder token in Tests/Setup.Tests.ps1) to the v2-token rule
allowlist. No other Tests/ or docs/ entries triggered findings with
the existing allowlist — no ongoing maintenance burden.
The third comment (v8.30.1 doesn't exist) was empirically refuted —
`curl https://api.github.com/repos/gitleaks/gitleaks/releases` returns
v8.30.1 as the current latest (published 2026-03-21). Gemini's training
data apparently predates the v8.22+ release line. Pinned version is
correct; see the pre-commit-hooks.io mirror for confirmation.
Baseline regenerated: 10 entries (was 9) — added the one Tests/Setup.Tests.ps1
placeholder that the path exclusion previously hid.
* security: round 2 Gemini feedback — word boundaries + global placeholders
Addresses Gemini's second-round review (2026-04-18T09:00:46Z):
1. Added \b word boundaries to netbox-v2-token regex (valid, MEDIUM)
Prevents partial matches within larger alphanumeric strings.
2. Moved v2 token placeholders from [rules.allowlist] to global
[allowlist].regexes (valid, MEDIUM). Now suppressed against every
rule including the default generic-api-key rule.
3. v1 token rule hardening (partially valid, MEDIUM):
- Added \b boundaries so exactly 40 hex chars are required
(a 48-hex string won't match the first 40 — precisely what
Gemini's (?![a-f0-9]) lookahead would have achieved, but
Go's regexp package doesn't support lookahead, so \b is
the portable equivalent — verified empirically: gitleaks
panics on Perl-syntax `(?!`)
- Added "api-key" (dash form) to keywords for broader trigger
- Added secretGroup = 1 so fingerprint uses the captured token
value, not the full match including the keyword prefix
Baseline regenerated: 9 entries (was 10 — the tightened v2 rule
no longer flags the nbt_abc123def456.ghijklmnopqrstuvwxyz1234567890
placeholder since it's now globally allowlisted).
Synthetic verification:
- 'Token = ''aaaa4bb9...40hex''': flagged ✓
- 'nbt_kVJSfSxl3xvO.b4K...': flagged ✓
- nbt_abc123def456.ghijk... placeholder: suppressed ✓
- @{ secret = 'abc...42hex' } (real-looking secret in hashtable):
now flagged by default generic-api-key (the overly-broad bracket
allowlist from v1 no longer hides it) ✓
* security: add leading \b to v1 token rule (round 3 Gemini)
Without a leading word boundary, the v1 token regex would partial-match
the substring 'Token' inside identifiers like PowerShell's
`$CancellationToken` or .NET's `CancellationToken`, producing false
positives on any 40-hex value assigned to such a variable.
Verified empirically:
$CancellationToken = '<40-hex>'
Before: flagged by netbox-v1-token rule (FP)
After: flagged only by generic-api-key entropy rule (correct default
gitleaks behavior; my custom rule now correctly abstains)
`$Token = '<40-hex>'` still flagged by netbox-v1-token (intended).
Baseline unchanged at 9 entries — all historic findings use `Token =` or
equivalent identifiers that already start at a word boundary.1 parent 7b42e36 commit ee93fc8
5 files changed
Lines changed: 630 additions & 0 deletions
File tree
- .github/workflows
- docs/superpowers/reviews
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
0 commit comments