Parent : [[INDEX-CYBERSECU]]
Anti-Pattern : Secrets hardcodés dans les fichiers .env
Description
Stocker des secrets de production (SESSION_SECRET, DATABASE_URL avec mot de passe, qBittorrent password) dans des fichiers .env qui sont commités dans le repository Git. C'est une faille de sécurité critique car ces secrets sont exposés à quiconque accède au code source.
Exemple réel - Archive Ygg
# .env — Débrideur CloudTorrent
PORT=3000
NODE_ENV=development
SESSION_SECRET=supersecretkeychangeinproduction # ❌ Secret prévisible
# Database (mot de passe vide en root)
DATABASE_URL="mysql://root:@localhost:3306/debrid" # ❌ Root sans mot de passe
# qBittorrent (credentials par défaut)
QBITTORRENT_USERNAME=admin
QBITTORRENT_PASSWORD=adminadmin # ❌ Password par défaut
Fichiers trouvés dans l'archive :
01_SOURCE_CODE/02g_debrideur/.env04_CREDENTIALS/04e_configs_secrets/debrideur_dotenv04_CREDENTIALS/04e_configs_secrets/debrideur_dotenv_example
Conséquences
- Compromission complète : Accès root à la base de données, accès admin qBittorrent
- Hijacking de sessions : SESSION_SECRET connu = forgery de cookies de session
- Vol de fichiers : Accès qBittorrent = téléchargement de tous les torrents stockés
- Persistence : Les secrets restent dans l'historique Git même après suppression
Quand c'est acceptable
JAMAIS en production. En développement local, utiliser des secrets temporaires et différents de la production.
Solution
# .env.example — Template SANS secrets réels
PORT=3000
NODE_ENV=development
SESSION_SECRET= # À remplir via variable d'environnement ou secret manager
DATABASE_URL= # À remplir via variable d'environnement
QBITTORRENT_USERNAME= # À remplir
QBITTORRENT_PASSWORD= # À remplir
# .gitignore
.env
.env.local
.env.production
*.pem
*.key
// app.js — Validation des secrets au démarrage
function validateSecrets() {
const required = [
'SESSION_SECRET',
'DATABASE_URL',
'QBITTORRENT_USERNAME',
'QBITTORRENT_PASSWORD'
];
for (const secret of required) {
const value = process.env[secret];
if (!value || value === '') {
throw new Error(`Variable d'environnement manquante: ${secret}`);
}
// Détecter les valeurs par défaut dangereuses
const dangerous = ['admin', 'password', 'secret', '123456', 'localhost'];
if (dangerous.some(d => value.toLowerCase().includes(d))) {
console.warn(`⚠️ ${secret} contient une valeur par défaut dangereuse`);
}
}
// Vérifier la force du SESSION_SECRET
if (process.env.SESSION_SECRET.length < 32) {
throw new Error('SESSION_SECRET doit faire au moins 32 caractères');
}
}
// Au démarrage
try {
validateSecrets();
} catch (e) {
console.error('❌ Configuration invalide:', e.message);
process.exit(1);
}
// Secret rotation
const crypto = require('crypto');
// Générer un secret fort
const generateSecret = () => crypto.randomBytes(64).toString('hex');
// Rotation périodique (tous les 90 jours)
setInterval(() => {
const newSecret = generateSecret();
// Mettre à jour le secret via l'API du secret manager
secretManager.update('SESSION_SECRET', newSecret);
}, 90 * 24 * 60 * 60 * 1000);
Checklist de validation
-
.envdans.gitignore -
.env.exampleavec des placeholders vides - Validation des secrets au démarrage (longueur, pas de valeurs par défaut)
- Secret manager en production (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault)
- Rotation automatique des secrets (tous les 90 jours)
- Pas de root sans mot de passe en base de données
- Credentials qBittorrent changées des valeurs par défaut
- Audit des accès aux secrets
Références
- Archive Ygg :
01_SOURCE_CODE/02g_debrideur/.env,04_CREDENTIALS/04e_configs_secrets/ - OWASP : Secret Management
- CWE-798 : Use of Hard-coded Credentials