Skip to content

Commit bdcd336

Browse files
author
ArkForge
committed
security: hook pre-commit détection secrets
1 parent 55e7191 commit bdcd336

1 file changed

Lines changed: 137 additions & 0 deletions

File tree

.githooks/pre-commit

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env bash
2+
# pre-commit — détection de secrets avant chaque commit
3+
# Installé via : git config core.hooksPath .githooks
4+
5+
set -euo pipefail
6+
7+
RED='\033[0;31m'
8+
YELLOW='\033[1;33m'
9+
NC='\033[0m'
10+
11+
ERRORS=0
12+
13+
# Fichiers stagés (hors suppressions, hors hook lui-même)
14+
STAGED=$(git diff --cached --name-only --diff-filter=ACMR | grep -v "^\.githooks/" || true)
15+
16+
if [ -z "$STAGED" ]; then
17+
exit 0
18+
fi
19+
20+
check() {
21+
local label="$1"
22+
local pattern="$2"
23+
local exclude="${3:-}"
24+
25+
if [ -n "$exclude" ]; then
26+
matches=$(echo "$STAGED" | xargs grep -lP "$pattern" 2>/dev/null | grep -vE "$exclude" || true)
27+
else
28+
matches=$(echo "$STAGED" | xargs grep -lP "$pattern" 2>/dev/null || true)
29+
fi
30+
31+
if [ -n "$matches" ]; then
32+
echo -e "${RED}[SECRET DETECTED] $label${NC}"
33+
echo "$matches" | while read -r f; do
34+
echo -e " ${YELLOW}$f${NC}"
35+
grep -nP "$pattern" "$f" 2>/dev/null | grep -vE "${exclude:-^$}" | head -3 | sed 's/^/ /'
36+
done
37+
ERRORS=$((ERRORS + 1))
38+
fi
39+
}
40+
41+
echo "Scanning staged files for secrets..."
42+
43+
# Clés privées PEM
44+
check "Clé privée PEM" \
45+
"-----BEGIN (EC |RSA |OPENSSH |DSA )?PRIVATE KEY-----"
46+
47+
# Stripe live
48+
check "Stripe live key (sk_live_)" \
49+
"sk_live_[A-Za-z0-9]{20,}" \
50+
"\.env\.example|test_.*\.py|\.md$"
51+
52+
# Stripe restricted live
53+
check "Stripe restricted live (rk_live_)" \
54+
"rk_live_[A-Za-z0-9]{20,}" \
55+
"\.env\.example|test_.*\.py|\.md$"
56+
57+
# GitHub tokens
58+
check "GitHub token (ghp_/ghs_/gho_/github_pat_)" \
59+
"gh[pso]_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{80,}"
60+
61+
# AWS keys
62+
check "AWS access key (AKIA)" \
63+
"AKIA[0-9A-Z]{16}"
64+
65+
# Anthropic / OpenAI
66+
check "Anthropic API key (sk-ant-)" \
67+
"sk-ant-[A-Za-z0-9\-]{20,}" \
68+
"\.env\.example|test_.*\.py|\.md$"
69+
70+
check "OpenAI API key (sk-...T3Blb)" \
71+
"sk-[A-Za-z0-9]{48}" \
72+
"\.env\.example|test_.*\.py|\.md$"
73+
74+
# Mots de passe hardcodés dans le code (toutes extensions)
75+
check "Password hardcodé (code)" \
76+
"(password|passwd|imap_password|smtp_password|db_password)\s*=\s*['\"][^'\"]{8,}['\"]" \
77+
"test_.*\.py|conftest\.py|\.env\.example|config\.py"
78+
79+
# Mots de passe dans les docs/README/commentaires
80+
# Détecte "password: valeur_reelle" mais pas "password: xxx", "password: <...>", "password: your_"
81+
check "Password dans doc/commentaire" \
82+
"(password|mot de passe|mdp|pwd|passwd)\s*[:=]\s*(?!xxx|your_|<|\.\.\.|\*|example|changeme|placeholder|CHANGE|SECRET)[^\s'\"<]{6,}" \
83+
"\.env\.example|test_.*\.py|conftest\.py"
84+
85+
# URLs avec credentials embarqués (http://user:pass@host)
86+
check "Credentials dans URL" \
87+
"https?://[^@\s]{3,}:[^@\s]{3,}@[a-zA-Z0-9]" \
88+
"\.env\.example|test_.*\.py"
89+
90+
# Tokens JWT hardcodés (eyJ... = header base64 d'un JWT)
91+
check "JWT token hardcodé" \
92+
"eyJ[A-Za-z0-9+/]{30,}\.[A-Za-z0-9+/]{30,}" \
93+
"test_.*\.py|conftest\.py|\.env\.example"
94+
95+
# Variables d'environnement avec vraie valeur dans les docs
96+
# Détecte FOO_SECRET=valeur_reelle mais pas FOO_SECRET=sk_test_fake ou FOO=xxx
97+
check "Variable secrète avec valeur réelle dans doc" \
98+
"^(STRIPE_LIVE|STRIPE_SECRET|IMAP_PASSWORD|SMTP_PASSWORD|TELEGRAM_BOT_TOKEN|INTERNAL_SECRET|VAULT_MASTER_KEY)\s*=\s*(?!sk_test_|sk_live_xxx|your_|xxx|<|\.\.\.|\*|changeme)[^\s]{8,}" \
99+
"\.env\.example|test_.*\.py|config\.py"
100+
101+
# Clés Mistral / autres LLM
102+
check "Mistral API key" \
103+
"[A-Za-z0-9]{32,}(mistral|MISTRAL)" \
104+
"\.env\.example|test_.*\.py"
105+
106+
# Emails personnels dans les docs (hors domaines attendus)
107+
check "Email personnel dans doc" \
108+
"[a-zA-Z0-9._%+-]{4,}@(?!(arkforge\.fr|example\.(com|org)|test\.com|gmail\.com|mailbox\.org|busilezas))[a-zA-Z0-9.-]+\.(fr|com|io|net|org|dev)" \
109+
"test_.*\.py|conftest\.py|\.env\.example|certs/"
110+
111+
# Fichiers suspects stagés directement
112+
for f in $STAGED; do
113+
case "$f" in
114+
*.pem|*.key|*.p12|*.pfx|*.jks|*.keystore)
115+
# Autoriser uniquement les certs publics connus
116+
if [[ "$f" != *"certs/cacert.pem"* && "$f" != *"certs/tsa.crt"* ]]; then
117+
echo -e "${RED}[SECRET DETECTED] Fichier clé/cert stagé : $f${NC}"
118+
ERRORS=$((ERRORS + 1))
119+
fi
120+
;;
121+
.env|.env.local|.env.production|config/settings.env)
122+
echo -e "${RED}[SECRET DETECTED] Fichier env stagé : $f${NC}"
123+
ERRORS=$((ERRORS + 1))
124+
;;
125+
esac
126+
done
127+
128+
if [ "$ERRORS" -gt 0 ]; then
129+
echo ""
130+
echo -e "${RED}COMMIT BLOQUÉ — $ERRORS type(s) de secret détecté(s).${NC}"
131+
echo "Retirez les secrets des fichiers stagés avant de committer."
132+
echo "Si c'est un faux positif, utilisez : git commit --no-verify (déconseillé)"
133+
exit 1
134+
fi
135+
136+
echo "OK — aucun secret détecté."
137+
exit 0

0 commit comments

Comments
 (0)