First-attempt commit3a5c6e184only captured the .gitignore change; the pre-commit hook silently dropped the 343 staged moves/deletes during lint-staged's "no matching task" path. This commit re-applies the intended J1 content on top ofbec75f143(which was pushed in parallel). Uses --no-verify because: - J1 only touches .md/.json/.log/.png/binaries — zero code that would benefit from lint-staged, typecheck, or vitest - The hook demonstrated it corrupts pure-rename commits in this repo - Explicitly authorized by user for this one commit Changes (343 total: 169 deletions + 174 renames): Binaries purged (~167 MB): - veza-backend-api/{server,modern-server,encrypt_oauth_tokens,seed,seed-v2} Generated reports purged: - 9 apps/web/lint_report*.json (~32 MB) - 8 apps/web/tsc_*.{log,txt} + ts_*.log (TS error snapshots) - 3 apps/web/storybook_*.json (1375+ stored errors) - apps/web/{build_errors*,build_output,final_errors}.txt - 70 veza-backend-api/coverage*.out + coverage_groups/ (~4 MB) - 3 veza-backend-api/internal/handlers/*.bak Root cleanup: - 54 audit-*.png (visual regression baselines, ~11 MB) - 9 stale MVP-era scripts (Jan 27, hardcoded v0.101): start_{iteration,mvp,recovery}.sh, test_{mvp_endpoints,protected_endpoints,user_journey}.sh, validate_v0101.sh, verify_logs_setup.sh, gen_hash.py Session docs archived (not deleted — preserved under docs/archive/): - 78 apps/web/*.md → docs/archive/frontend-sessions-2026/ - 43 veza-backend-api/*.md → docs/archive/backend-sessions-2026/ - 53 docs/{RETROSPECTIVE_V,SMOKE_TEST_V,PLAN_V0_,V0_*_RELEASE_SCOPE, AUDIT_,PLAN_ACTION_AUDIT,REMEDIATION_PROGRESS}*.md → docs/archive/v0-history/ README.md and CONTRIBUTING.md preserved in apps/web/ and veza-backend-api/. Note: The .gitignore rules preventing recurrence were already pushed in3a5c6e184and remain in place — this commit does not modify .gitignore. Refs: AUDIT_REPORT.md §11
14 KiB
Fix Sécurité JWT — Rapport complet
Date: 2025-01-27
Faille corrigée: JWT_SECRET avec valeur par défaut hardcodée
Sévérité: 🔴 CRITIQUE
Statut: ✅ CORRIGÉ
1. Fichiers impactés
Fichiers modifiés
-
✅
internal/config/config.go(lignes 115-122)- Avant:
jwtSecret := getEnv("JWT_SECRET", "your-super-secret-jwt-key") - Après:
jwtSecret := getEnvRequired("JWT_SECRET") - Avant:
DatabaseURL: getEnv("DATABASE_URL", "postgresql://veza:password@localhost:5432/veza_db") - Après:
DatabaseURL: getEnvRequired("DATABASE_URL")
- Avant:
-
✅
internal/config/config_test.go(nouveaux tests ajoutés)- Ajout de
TestNewConfig_RequiresJWTSecret()(ligne 287) - Ajout de
TestNewConfig_RequiresDatabaseURL()(ligne 310)
- Ajout de
-
✅
cmd/migrate_tool/main.go(lignes 16-20)- Avant:
Password: getEnv("DB_PASSWORD", "veza") - Après:
Password: getEnvRequired("DB_PASSWORD") - Ajout de la fonction
getEnvRequired()dans ce fichier
- Avant:
-
✅
.env.example(nouveau fichier créé)- Documentation complète des variables d'environnement
- JWT_SECRET et DATABASE_URL marqués comme REQUIS
Fichiers analysés (non modifiés)
internal/config/config.go- FonctionLoad()utilise déjàgetEnvRequired()✅internal/services/jwt_service.go- Gère correctement l'absence de secret ✅internal/config/secrets.go- Liste des secrets correctement définie ✅
2. Autres secrets avec défaut dangereux trouvés
| Variable | Fichier | Action | Statut |
|---|---|---|---|
| JWT_SECRET | internal/config/config.go:116 |
Remplacé par getEnvRequired() |
✅ CORRIGÉ |
| DATABASE_URL | internal/config/config.go:122 |
Remplacé par getEnvRequired() (contient password) |
✅ CORRIGÉ |
| DB_PASSWORD | cmd/migrate_tool/main.go:20 |
Remplacé par getEnvRequired() |
✅ CORRIGÉ |
| DB_PASSWORD (test) | internal/database/pool_test.go:23,86 |
Acceptable (fichier de test uniquement) | ✅ OK |
Variables avec défaut acceptable (gardées)
| Variable | Fichier | Justification |
|---|---|---|
| PORT | config.go:113 |
Valeur par défaut "8080" acceptable pour dev local |
| LOG_LEVEL | config.go:110 |
Valeur par défaut "INFO" acceptable |
| REDIS_URL | config.go:121 |
URL locale par défaut acceptable pour dev |
| CORS_ORIGINS | config.go:101 |
Défaut "*" acceptable pour dev local |
| CHAT_JWT_SECRET | config.go:120 |
Fallback vers JWT_SECRET (maintenant requis) ✅ |
3. Code du fix
3.1 Fonction getEnvRequired() (déjà existante)
// getEnvRequired récupère une variable d'environnement requise (panique si absente)
func getEnvRequired(key string) string {
value := os.Getenv(key)
if value == "" {
panic(fmt.Sprintf("Required environment variable %s is not set", key))
}
return value
}
3.2 Modification dans NewConfig()
AVANT (ligne 116):
jwtSecret := getEnv("JWT_SECRET", "your-super-secret-jwt-key")
APRÈS (ligne 115-116):
// SECURITY: JWT_SECRET est REQUIS - pas de valeur par défaut pour éviter les failles de sécurité
jwtSecret := getEnvRequired("JWT_SECRET")
AVANT (ligne 122):
DatabaseURL: getEnv("DATABASE_URL", "postgresql://veza:password@localhost:5432/veza_db"),
APRÈS (ligne 122-123):
// SECURITY: DATABASE_URL est REQUIS - contient des credentials sensibles
DatabaseURL: getEnvRequired("DATABASE_URL"),
3.3 Correction dans cmd/migrate_tool/main.go
AVANT:
Password: getEnv("DB_PASSWORD", "veza"),
APRÈS:
// SECURITY: DB_PASSWORD is required - no default value to prevent security issues
dbPassword := getEnvRequired("DB_PASSWORD")
// ...
Password: dbPassword,
Avec ajout de la fonction getEnvRequired() dans ce fichier.
4. Tests ajoutés
4.1 Test pour JWT_SECRET manquant
// TestNewConfig_RequiresJWTSecret vérifie que NewConfig() refuse de démarrer sans JWT_SECRET
// Ce test valide la correction de sécurité qui empêche l'utilisation d'une valeur par défaut hardcodée
func TestNewConfig_RequiresJWTSecret(t *testing.T) {
// Sauvegarder les valeurs originales
originalJWTSecret := os.Getenv("JWT_SECRET")
originalDatabaseURL := os.Getenv("DATABASE_URL")
// Nettoyer après le test
defer func() {
if originalJWTSecret != "" {
os.Setenv("JWT_SECRET", originalJWTSecret)
} else {
os.Unsetenv("JWT_SECRET")
}
if originalDatabaseURL != "" {
os.Setenv("DATABASE_URL", originalDatabaseURL)
} else {
os.Unsetenv("DATABASE_URL")
}
}()
// Supprimer JWT_SECRET - devrait causer un panic
os.Unsetenv("JWT_SECRET")
// Définir DATABASE_URL pour éviter un panic sur cette variable (on teste seulement JWT_SECRET)
os.Setenv("DATABASE_URL", "postgresql://test:test@localhost:5432/test_db")
// Devrait paniquer car JWT_SECRET est requis
assert.Panics(t, func() {
_, _ = NewConfig()
}, "NewConfig should panic when JWT_SECRET is missing")
}
4.2 Test pour DATABASE_URL manquant
// TestNewConfig_RequiresDatabaseURL vérifie que NewConfig() refuse de démarrer sans DATABASE_URL
// Ce test valide la correction de sécurité qui empêche l'utilisation d'une valeur par défaut avec credentials
func TestNewConfig_RequiresDatabaseURL(t *testing.T) {
// Sauvegarder les valeurs originales
originalJWTSecret := os.Getenv("JWT_SECRET")
originalDatabaseURL := os.Getenv("DATABASE_URL")
// Nettoyer après le test
defer func() {
if originalJWTSecret != "" {
os.Setenv("JWT_SECRET", originalJWTSecret)
} else {
os.Unsetenv("JWT_SECRET")
}
if originalDatabaseURL != "" {
os.Setenv("DATABASE_URL", originalDatabaseURL)
} else {
os.Unsetenv("DATABASE_URL")
}
}()
// Définir JWT_SECRET (minimum 32 caractères pour passer la validation)
os.Setenv("JWT_SECRET", "test-jwt-secret-key-minimum-32-characters-long")
// Supprimer DATABASE_URL - devrait causer un panic
os.Unsetenv("DATABASE_URL")
// Devrait paniquer car DATABASE_URL est requis
assert.Panics(t, func() {
_, _ = NewConfig()
}, "NewConfig should panic when DATABASE_URL is missing")
}
4.3 Résultat des tests
$ go test ./internal/config -run TestNewConfig_RequiresJWTSecret -v
=== RUN TestNewConfig_RequiresJWTSecret
--- PASS: TestNewConfig_RequiresJWTSecret (0.00s)
PASS
ok veza-backend-api/internal/config 0.015s
✅ Tests passent avec succès
5. Documentation mise à jour
5.1 Fichier .env.example créé
Nouveau fichier créé : veza-backend-api/.env.example
Contenu clé:
- Section "VARIABLES REQUISES" avec JWT_SECRET et DATABASE_URL
- Instructions claires pour générer JWT_SECRET
- Toutes les variables optionnelles documentées avec leurs valeurs par défaut
- Commentaires explicatifs pour chaque variable
Extrait:
# ============================================
# VARIABLES REQUISES (DOIVENT ÊTRE DÉFINIES)
# ============================================
# JWT_SECRET - REQUIS - Secret pour signer et valider les tokens JWT
# DOIT être défini - minimum 32 caractères pour la sécurité
# Générer avec: openssl rand -base64 32
JWT_SECRET=
# DATABASE_URL - REQUIS - URL de connexion à la base de données PostgreSQL
# Format: postgresql://user:password@host:port/database?sslmode=disable
# DOIT être défini - contient des credentials sensibles
DATABASE_URL=
5.2 Documentation existante
- ✅
internal/config/docs.go- JWT_SECRET déjà marqué commeRequired: true - ✅
internal/config/docs_test.go- Tests vérifient que JWT_SECRET est requis - ⚠️ README principal - Ne mentionne pas les variables d'environnement (non critique)
6. Audit secrets supplémentaires
6.1 Recherche exhaustive effectuée
Commandes exécutées:
grep -r "JWT_SECRET" veza-backend-api/
grep -r "jwt.*secret\|secret.*jwt" veza-backend-api/ -i
grep -r "getEnv.*secret\|getEnv.*JWT" veza-backend-api/ -i
grep -r "your-super-secret" veza-backend-api/ -i
grep -r "password\|secret\|api_key" veza-backend-api/internal/config/ -i
6.2 Résultats de l'audit
✅ Secrets correctement gérés
| Secret | Fichier | Statut |
|---|---|---|
| JWT_SECRET | internal/config/config.go |
✅ Corrigé (getEnvRequired) |
| DATABASE_URL | internal/config/config.go |
✅ Corrigé (getEnvRequired) |
| DB_PASSWORD | cmd/migrate_tool/main.go |
✅ Corrigé (getEnvRequired) |
| JWT_SECRET | internal/config/Load() |
✅ Déjà requis (getEnvRequired) |
| DB_PASSWORD | internal/config/Load() |
✅ Déjà requis (getEnvRequired) |
✅ Secrets dans les tests (acceptables)
| Secret | Fichier | Statut |
|---|---|---|
| DB_PASSWORD | internal/database/pool_test.go |
✅ OK (fichier de test uniquement) |
| JWT_SECRET | internal/config/testutils.go |
✅ OK (utilitaire de test) |
✅ Secrets correctement masqués dans les logs
internal/config/secrets.go- FonctionMaskSecret()implémentéeinternal/config/config.go:549- JWT_SECRET masqué dans les logsinternal/config/config.go:550- DATABASE_URL masqué dans les logs
6.3 Aucun secret hardcodé trouvé
✅ Aucune autre valeur par défaut dangereuse trouvée dans le code de production
7. Commandes pour appliquer
7.1 Vérification des modifications
cd veza-backend-api
# Vérifier que le code compile
go build ./internal/config/...
# Exécuter les tests
go test ./internal/config -run TestNewConfig_Requires -v
# Vérifier tous les tests de config
go test ./internal/config/... -v
7.2 Application en production
⚠️ IMPORTANT: Cette correction est BREAKING pour les environnements qui n'ont pas défini JWT_SECRET.
Étapes de déploiement:
-
Avant le déploiement:
# Vérifier que JWT_SECRET est défini dans tous les environnements echo $JWT_SECRET # Ne doit pas être vide echo $DATABASE_URL # Ne doit pas être vide -
Déployer le code:
git add internal/config/config.go internal/config/config_test.go .env.example cmd/migrate_tool/main.go git commit -m "security: Remove hardcoded JWT_SECRET default value - Replace getEnv() with getEnvRequired() for JWT_SECRET in NewConfig() - Replace getEnv() with getEnvRequired() for DATABASE_URL (contains credentials) - Add tests to verify panic when required variables are missing - Create .env.example with clear documentation of required variables - Fix DB_PASSWORD default in migrate_tool BREAKING CHANGE: JWT_SECRET and DATABASE_URL are now required. Application will panic at startup if these variables are not set." git push -
Vérifier le démarrage:
# L'application doit démarrer normalement si les variables sont définies # L'application doit PANIC si JWT_SECRET ou DATABASE_URL sont absents
7.3 Migration des environnements existants
Pour les environnements qui utilisent encore la valeur par défaut:
-
Générer un nouveau JWT_SECRET:
openssl rand -base64 32 -
Définir la variable d'environnement:
export JWT_SECRET="<valeur-générée>" # Ou dans .env: echo "JWT_SECRET=<valeur-générée>" >> .env -
Redémarrer l'application
8. Impact et compatibilité
8.1 Rétrocompatibilité
✅ Rétrocompatible pour les environnements déjà configurés correctement :
- Si
JWT_SECRETest défini → Aucun changement de comportement - Si
DATABASE_URLest défini → Aucun changement de comportement
❌ Breaking change pour les environnements non configurés :
- Si
JWT_SECRETn'est pas défini → Application panic au démarrage - Si
DATABASE_URLn'est pas défini → Application panic au démarrage
8.2 Message d'erreur
En cas de variable manquante, l'application affichera :
panic: Required environment variable JWT_SECRET is not set
ou
panic: Required environment variable DATABASE_URL is not set
Avantage: Message clair et explicite, pas de crash silencieux.
9. Validation finale
✅ Checklist de sécurité
- JWT_SECRET n'a plus de valeur par défaut hardcodée
- DATABASE_URL n'a plus de valeur par défaut avec credentials
- DB_PASSWORD dans migrate_tool corrigé
- Tests ajoutés pour vérifier le comportement
- Documentation créée (.env.example)
- Aucun autre secret avec défaut dangereux trouvé
- Code compile sans erreur
- Tests passent
✅ Tests de validation
# Test 1: Vérifier que NewConfig() panic sans JWT_SECRET
$ go test ./internal/config -run TestNewConfig_RequiresJWTSecret -v
PASS
# Test 2: Vérifier que NewConfig() panic sans DATABASE_URL
$ go test ./internal/config -run TestNewConfig_RequiresDatabaseURL -v
PASS
# Test 3: Compilation
$ go build ./internal/config/...
OK
10. Conclusion
✅ Faille de sécurité corrigée avec succès
- 3 fichiers modifiés pour corriger les valeurs par défaut dangereuses
- 2 nouveaux tests ajoutés pour valider le comportement
- 1 fichier de documentation créé (.env.example)
- Aucun secret hardcodé restant dans le code de production
L'application refuse maintenant de démarrer si JWT_SECRET ou DATABASE_URL ne sont pas définis, empêchant ainsi l'utilisation accidentelle de valeurs par défaut non sécurisées.
Rapport généré le: 2025-01-27
Validé par: Tests automatisés ✅