Skip to content

Sécurité

English version
Retour au sommaire


Headers HTTP de sécurité

Les headers suivants sont appliqués à toutes les réponses via la configuration Next.js (next.config.ts) :

Header Valeur Protection
X-Content-Type-Options nosniff Empêche le MIME sniffing
X-Frame-Options DENY Empêche le clickjacking (iframes)
Referrer-Policy strict-origin-when-cross-origin Contrôle les infos de référent
Permissions-Policy camera=(), geolocation=() Désactive les APIs non utilisées

Note sur Permissions-Policy : le microphone est autorisé (nécessaire pour l'interface locuteur). Seules la caméra et la géolocalisation sont explicitement interdites.


Content Security Policy (CSP)

La CSP est configurée dynamiquement dans src/middleware.ts et src/proxy.ts. Elle inclut le domaine CDN audio.

Directives clés

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-inline';    ← unsafe-inline requis pour Next.js
  style-src 'self' 'unsafe-inline';
  img-src 'self' https: data:;
  media-src 'self' [AUDIO_CDN_DOMAIN] blob:;
  connect-src 'self' [AUDIO_CDN_DOMAIN];
  worker-src 'self' blob:;              ← pour le Service Worker
  frame-ancestors 'none';              ← équivalent X-Frame-Options: DENY

AUDIO_CDN_DOMAIN est injecté au runtime depuis la variable d'environnement. Si ce domaine est absent de media-src, les audios sont bloqués par le navigateur sans erreur visible pour l'utilisateur.

Diagnostic CSP

Si les audios ne se lisent pas, cherchez dans la console du navigateur :

Refused to connect to 'https://racines-s3.id2real.net/audios/...' 
because it violates the following Content Security Policy directive: "connect-src 'self'"

Solution : vérifiez que AUDIO_CDN_DOMAIN est correctement défini.


Authentification

Voir la documentation complète : 07-authentification.md

Résumé des protections : - Mots de passe hashés PBKDF2 (100k itérations) - JWT HS256 dans cookie HttpOnly (pas de XSS possible) - Comparaison en temps constant (pas de timing attack)


Validation des entrées

API Routes

Toutes les API routes valident leurs entrées avant traitement. Exemple typique :

// Validation du corps de requête
const { name, email, subject, message } = await request.json();

if (!name || !email || !subject || !message) {
  return Response.json({ error: 'Champs manquants' }, { status: 400 });
}

if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
  return Response.json({ error: 'Email invalide' }, { status: 400 });
}

Requêtes SQL paramétrées

Jamais de concaténation de chaînes dans les requêtes SQL. Toujours des paramètres :

// ✅ Correct
const result = await pool.query(
  'SELECT * FROM languages WHERE access_code = $1 AND is_active = true',
  [code]  // paramètre
);

// ❌ Incorrect (injection SQL possible)
const result = await pool.query(
  `SELECT * FROM languages WHERE access_code = '${code}'`
);

Protection du proxy audio — path traversal

L'endpoint /api/audio/serve/[[...path]] proxy les fichiers MinIO. Il valide le chemin pour prévenir les attaques de traversal de répertoire :

// src/app/api/audio/serve/[[...path]]/route.ts
const path = params.path?.join('/') || '';

// Validation : pas de '..' dans le chemin
if (path.includes('..') || path.startsWith('/')) {
  return Response.json({ error: 'Chemin invalide' }, { status: 400 });
}

Formulaire de contact — anti-spam

Honeypot

Un champ caché (website) est présent dans le HTML du formulaire. Les robots le remplissent automatiquement ; les humains ne le voient pas.

// Si le champ honeypot est rempli → rejet silencieux
if (body.website) {
  return Response.json({ success: true }); // Fausse réponse de succès
}

Rate limiting

Maximum 1 message par heure par adresse IP : - IP extraite du header X-Forwarded-For (via Traefik) - Compteur stocké en mémoire (ou Redis si configuré) - Réponse 429 : "Trop de messages envoyés, réessayez dans une heure"


Secrets et variables d'environnement

Règles absolues : - Jamais de secrets dans le code source - Jamais de fichiers .env commités dans Git (.gitignore configuré) - Les secrets de production sont dans les variables GitLab CI/CD et le fichier .app_env sur le serveur

Variables sensibles : - JWT_SECRET : si compromis, changez-le — toutes les sessions actives sont invalidées - MINIO_SECRET_KEY et MINIO_ACCESS_KEY : accès complet au stockage audio - PG_PASSWORD : accès à la base de données


CORS

MinIO est configuré avec des règles CORS strictes via Traefik middleware : - Seule(s) l'URL(s) de l'application sont autorisées en Access-Control-Allow-Origin - Méthodes autorisées : GET, HEAD

Le serveur Next.js n'a pas de configuration CORS particulière (les API routes ne sont accédées que depuis le frontend ou depuis le serveur lui-même).


Checklist sécurité

Point Status
Mots de passe hashés (PBKDF2)
JWT dans cookie HttpOnly
CSP configurée
X-Frame-Options: DENY
Requêtes SQL paramétrées
Validation côté serveur
Protection path traversal (proxy audio)
Honeypot anti-spam (contact)
Rate limiting (contact)
Secrets hors du code source
Pas de logs de données sensibles

Étapes suivantes