Aller au contenu principal

Politique de Sécurité

Ce fichier est la référence sécurité de la plateforme. Il répond à trois questions : comment nous nous protégeons, quoi faire si quelque chose est compromis, et comment nous gardons les dépendances saines. C'est pour nous d'abord, pour un auditeur ensuite.

Surface d'attaque — Ce qui est exposé publiquement

Cette section documente exactement ce qui est accessible sans authentification :

Endpoint / RessourcePublicAuthentifiéAdmin uniquement
POST /api/v1/auth/login
POST /api/v1/auth/register
GET /api/v1/internships
POST /api/v1/applications✅ (L3/M)
GET /api/v1/admin/users
Frontends (HTML/JS/CSS)

Règle : Tout ce qui n'est pas dans la colonne "Public" doit être refusé par le backend si le token est absent ou invalide. Le frontend n'est pas une protection.


Protection contre les attaques communes

Cette section documente chaque mesure en place et comment la vérifier :

Injection SQL

  • Mesure : Client Supabase natif avec requêtes paramétrées. Aucune requête SQL construite à la main avec des f-strings.
  • Ce qui est interdit : f"SELECT * FROM users WHERE email='{email}'" — jamais.
  • Comment vérifier : Le SDK Supabase gère automatiquement la protection contre les injections. Les requêtes brutes (raw) doivent être évitées.

XSS (Cross-Site Scripting)

  • Mesure : React échappe automatiquement le contenu des variables dans le JSX.
  • Ce qui est interdit : dangerouslySetInnerHTML sans sanitization explicite.
  • Tokens en JSON body : Les tokens sont retournés dans le corps de la réponse JSON. Le stockage côté client est géré par le frontend (localStorage ou mémoire JS).

CSRF (Cross-Site Request Forgery)

  • Mesure : SameSite=Lax sur les cookies + validation de l'origine dans les headers CORS côté backend.
  • Comment ça protège : Un site tiers ne peut pas déclencher des requêtes authentifiées à notre API depuis le navigateur d'un utilisateur connecté.

CORS

  • Mesure : Liste blanche explicite des origines autorisées dans FastAPI (jamais allow_origins=["*"] avec credentials).
  • Où c'est configuré : app/main.py, constante ALLOWED_ORIGINS.
  • Comment vérifier : Tester depuis une origine non listée → doit recevoir une erreur CORS.

Rate Limiting

  • Mesure : Nous limitons les endpoints sensibles (login, register, forgot-password) pour bloquer le brute force.
  • Outil recommandé : slowapi (FastAPI) ou rate limiting au niveau du reverse proxy (Nginx/Dokploy).
  • Seuils minimaux : Login → 10 tentatives par minute par IP. Register → 5 par minute par IP.
  • Nous documentons ici les seuils exacts configurés.

Mots de passe

  • Mesure : Hachage avec bcrypt (coût minimum 12). Jamais MD5, jamais SHA1, jamais en clair.
  • Aucun mot de passe ne doit apparaître dans les logs — vérifier que le schéma Pydantic de login exclut le password des logs.

Logs d'audit — Ce qui est tracé

Cette section documente quelles actions sont loggées et où :

ActionLoggée ?Ce qui est enregistré
Login réussiDB table audit_logsuser_id, IP, timestamp
Login échouéDB table audit_logsemail tenté, IP, timestamp
LogoutDB table audit_logsuser_id, timestamp
Action admin (suppression, modification)DB table audit_logsadmin_id, action, target_id, timestamp
Erreurs 500SentryStack trace complète

Règle : Les logs d'audit ne doivent jamais contenir de données sensibles (mot de passe, token complet, données personnelles non nécessaires).

Durée de rétention : Définir ici combien de temps les logs sont conservés (ex : 90 jours).


Gestion des dépendances vulnérables

Le vrai risque silencieux : une lib que nous utilisons a une faille connue et nous ne le savons pas.

Procédure de vérification (à faire chaque mois)

Backend (Python) :

pip install pip-audit
pip-audit
# Liste les CVE connues dans nos dépendances

Frontend (Node.js) :

npm audit
# ou
npm audit --fix # Correction automatique des failles mineures

Règle de réponse selon la sévérité

SévéritéDélai de correctionResponsable
Critique (CVSS ≥ 9)24h maximumDev senior en priorité absolue
Haute (CVSS 7-9)1 semaineDev senior
Moyenne (CVSS 4-7)Prochain sprintN'importe quel dev
Faible (CVSS < 4)BacklogTraité quand possible

Configurer GitHub Dependabot pour recevoir automatiquement des PRs quand une dépendance a une faille connue. C'est gratuit et ça prend 5 minutes à configurer.


Procédure d'incident — Si quelque chose est compromis

C'est le document le plus important à lire avant qu'un incident arrive, pas pendant.

Scénario 1 : Secret/credential exposé (API key, DB password dans un commit)

  1. Immédiatement (< 5 min) : Révoquer et régénérer le credential compromis — avant même de comprendre comment c'est arrivé
  2. Dans l'heure : Vérifier les logs pour détecter une utilisation non autorisée du credential
  3. Dans la journée : Identifier comment c'est arrivé, corriger le processus (.gitignore, pre-commit hook, etc.)
  4. Nous documentons : Ajouter un post-mortem dans security/incidents/YYYY-MM-DD-description.md

Scénario 2 : Compte admin compromis

  1. Immédiatement : Changer le mot de passe du compte (la révocation de token est partielle — voir lacunes connues — le token actif reste valide jusqu'à expiration naturelle, soit 30 minutes pour l'access token)
  2. Immédiatement : Examiner les logs d'audit pour toutes les actions effectuées par ce compte dans les 24-72h précédentes

Scénario 3 : Faille découverte dans notre code

  1. Ne pas patcher en prod directement — créer une branche hotfix/
  2. Évaluer l'impact réel avant de paniquer : la faille est-elle exploitable ? A-t-elle été exploitée ?
  3. Patcher, tester sur staging, déployer
  4. Post-mortem obligatoire

Contacts d'urgence

Nous documentons ici (pas dans ce guide public, dans notre version interne) :

  • Qui appeler en dehors des heures de bureau
  • Accès Dokploy/Vercel en urgence (qui a les droits ?)
  • Si obligation légale de notification : contact CNIL

RGPD — Données personnelles collectées

Documenter ce que nous stockons et pourquoi — utile si quelqu'un demande la suppression de ses données ou si la CNIL pose des questions.

DonnéeOù stockéePourquoiDurée de rétention
EmailDB usersAuthentificationJusqu'à suppression du compte
Mot de passe (haché)DB usersAuthentificationJusqu'à suppression du compte
CV (fichier)Stockage fichiersCandidatureJusqu'à suppression du compte
Logs d'activitéDB audit_logsSécurité90 jours

Procédure de suppression de compte : Nous documentons exactement quelles tables sont touchées quand un utilisateur demande la suppression de ses données (droit à l'effacement RGPD).


Ce qui n'est PAS encore en place (et c'est ok)

Être honnête sur ce qui manque évite de fausser un futur audit. Nous documentons les lacunes connues avec une date cible :

MesureStatutPrioritéDate cible
Pentest externe❌ Pas faitMoyenneQuand 500+ users actifs
2FA pour les admins❌ Pas implémentéHauteSprint 3
Chiffrement des fichiers au repos❌ Pas faitMoyennePhase 2
Audit trail complet🟡 PartielHauteSprint 2
Révocation immédiate des tokens✅ ImplémentéHauteSprint 2
Tokens en mémoire JS (pas localStorage)❌ À corrigerHauteSprint 1

Solutions à implémenter

Révocation des tokens JWT ✅ Implémenté

État actuel (Sprint 2) :

  • Redis est maintenant activé par défaut en production (REDIS_ENABLED=true)
  • Chaque token JWT contient un jti (JWT ID) unique
  • Les tokens révoqués sont stockés dans Redis avec clé revoked:jti:{jti} et TTL automatique
  • Le logout révoque immédiatement le token (pas d'attente de 15 min)
  • Endpoint /api/v1/auth/revoke disponible pour révoquer manuellement un token

Fonctionnalités implémentées :

  1. ✅ JTI ajouté dans le payload JWT
  2. ✅ Stockage Redis des tokens révoqués avec TTL
  3. ✅ Vérification de la révocation à chaque requête
  4. ✅ Endpoint POST /api/v1/auth/revoke
  5. ✅ Logout révoque le token immédiatement

Limitations restantes :

  • Pas de refresh token rotation (issue #14)

Stockage des tokens côté client

Problème actuel : L'access token est stocké dans localStorage côté frontend. Cette approche est vulnérable aux attaques XSS — tout script malveillant exécuté sur la page peut lire le localStorage et voler les tokens.

Solution à implémenter :

  1. Stocker l'access token en mémoire JavaScript (variable en mémoire, pas dans localStorage)
  2. Stocker le refresh token dans un cookie httpOnly avec les flags Secure et SameSite=Strict
  3. Le cookie httpOnly n'est pas accessible via JavaScript, ce qui protège contre le vol par XSS
  4. Implémenter un mécanisme de refresh automatique quand l'access token expire