Explorer
KNOW-ANT-007

Anti-Pattern : Secrets hardcodés dans les fichiers .env

Domaine
cybersecu
Type
anti-pattern
Priorité
P2

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/.env
  • 04_CREDENTIALS/04e_configs_secrets/debrideur_dotenv
  • 04_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

  • .env dans .gitignore
  • .env.example avec 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