Skip to content

Commit e108575

Browse files
authored
security: add gitleaks pre-commit hook and CI scan (#1)
Deploys org-wide secret-leak prevention following the 2026-04-17 Discord webhook URL incident (scraped from public karaoke-gen repo, used to spam malware in #releases). Layers: - .githooks/pre-commit — gitleaks protect --staged (fail fast) - .github/workflows/security.yml — reusable CI scanner - .gitleaks.toml — default rules + Discord-webhook rule + FP allowlist
1 parent 456342a commit e108575

3 files changed

Lines changed: 88 additions & 0 deletions

File tree

.githooks/pre-commit

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env bash
2+
# Nomad Karaoke shared pre-commit hook — scans staged changes for secrets.
3+
# Enable: git config core.hooksPath .githooks
4+
set -eu
5+
6+
if ! command -v gitleaks >/dev/null 2>&1; then
7+
printf '\033[33mwarn:\033[0m gitleaks not installed — skipping secret scan\n' >&2
8+
printf ' install: brew install gitleaks (or: https://github.com/gitleaks/gitleaks)\n' >&2
9+
exit 0
10+
fi
11+
12+
if ! gitleaks protect --staged --redact --no-banner; then
13+
printf '\n\033[31m✗ gitleaks detected a potential secret in staged changes.\033[0m\n' >&2
14+
printf ' • If real: remove it, rotate the credential, then re-commit.\n' >&2
15+
printf ' • If false positive: add an allowlist entry to .gitleaks.toml.\n' >&2
16+
printf ' • Emergency override (not recommended): git commit --no-verify\n' >&2
17+
exit 1
18+
fi

.github/workflows/security.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: Security
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
jobs:
9+
gitleaks:
10+
uses: nomadkaraoke/.github/.github/workflows/gitleaks.yml@main

.gitleaks.toml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Nomad Karaoke shared gitleaks config.
2+
# Extends the default ruleset with one custom rule for Discord webhooks
3+
# and an allowlist covering known false-positive paths.
4+
5+
title = "Nomad Karaoke"
6+
7+
[extend]
8+
useDefault = true
9+
10+
# ---------------------------------------------------------------------------
11+
# Custom rules
12+
# ---------------------------------------------------------------------------
13+
14+
[[rules]]
15+
id = "discord-webhook-url"
16+
description = "Discord webhook URL (token embedded in URL)"
17+
regex = '''https://(?:discord|discordapp)\.com/api/webhooks/\d{17,20}/[A-Za-z0-9_-]{60,}'''
18+
tags = ["discord", "webhook", "secret"]
19+
20+
# ---------------------------------------------------------------------------
21+
# Global allowlist
22+
# ---------------------------------------------------------------------------
23+
24+
[allowlist]
25+
description = "Paths and patterns that are known false positives"
26+
27+
paths = [
28+
'''(?i)\.(jpg|jpeg|png|gif|bmp|svg|webp|pdf|ico|mp3|mp4|wav|flac|m4a)$''',
29+
'''(^|/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|poetry\.lock|Podfile\.lock|Cargo\.lock|go\.sum|composer\.lock|Gemfile\.lock|uv\.lock)$''',
30+
'''(^|/)node_modules/''',
31+
'''(^|/)vendor/''',
32+
'''(^|/)wp-content/''',
33+
'''(^|/)\.dart_tool/''',
34+
'''(^|/)\.venv/''',
35+
'''(^|/)\.next/''',
36+
'''(^|/)build/''',
37+
'''(^|/)dist/''',
38+
'''(^|/)\.flutter-plugins''',
39+
'''(^|/)test[-_]?fixtures?/''',
40+
'''(^|/)tests?/.*conftest\.py$''',
41+
'''(^|/)tests?/.*test_[^/]+\.py$''',
42+
'''(^|/)tests?/.*_test\.py$''',
43+
'''(^|/).*\.(test|spec)\.(js|mjs|cjs|ts|tsx|jsx)$''',
44+
'''(^|/).*\.test\.local\.(js|mjs|cjs|ts|tsx|jsx|py)$''',
45+
'''(^|/).*\.local\.(js|mjs|cjs|ts|tsx|jsx|py)$''',
46+
]
47+
48+
regexes = [
49+
# Explicit placeholder patterns
50+
'''YOUR_[A-Z_]{2,}''',
51+
'''EXAMPLE_[A-Z_]{2,}''',
52+
'''PLACEHOLDER_[A-Z_]{2,}''',
53+
'''CHANGE[_-]?ME''',
54+
'''<[A-Z_]{2,}>''',
55+
'''\$\{[A-Z_]{2,}\}''',
56+
57+
# Firebase / GCP client API keys — public by design (identify the project,
58+
# access is gated by Firebase Security Rules).
59+
'''AIzaSy[A-Za-z0-9_-]{33}''',
60+
]

0 commit comments

Comments
 (0)