18 KiB
TaxActe — Corrections Sécurité Phase 1
Date : 2026-05-15
Agent : Agent Sécurité
Mission : Correction des 3 vulnérabilités critiques identifiées dans SECURITY-AUDIT.md
Résumé Exécutif
Vulnérabilités Corrigées
| Vulnérabilité | Statut | Fichiers Modifiés | Tests |
|---|---|---|---|
| 🚨 Rate Limiting Login | ✅ CORRIGÉ | src/lib/rate-limit.ts, src/app/api/auth/login/route.ts |
✅ 6 tests passent |
| 🚨 CSRF Protection | ✅ CORRIGÉ | src/lib/auth.ts, src/middleware.ts |
⚠️ À tester manuellement |
| 🚨 Security Headers | ✅ CORRIGÉ | next.config.ts |
⚠️ À tester avec securityheaders.com |
| ⚠️ Validation Zod API | ✅ CORRIGÉ | 7 routes /api/calcul/* |
⚠️ À tester avec inputs invalides |
Statut Global
- ✅ 4/4 corrections critiques implémentées
- ✅ Rate limiting : 5 tentatives / 15 min par IP
- ✅ CSRF : cookie
sameSite: strict+ validation origin - ✅ Security headers : CSP, HSTS, X-Frame-Options, etc.
- ✅ Validation Zod : 7 routes de calcul sécurisées
- ⚠️ Tests manuels requis (brute force, CSRF, headers score)
- ⚠️ npm audit non exécuté (permission refusée)
1. Rate Limiting Login (Jours 1-2)
Problème Identifié
Vulnérabilité : Pas de rate limiting sur /api/auth/login → brute force possible (audit section 1.4).
Un attaquant pouvait tenter 1000+ requêtes/seconde pour deviner un mot de passe.
Solution Implémentée
Fichier créé : /Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/lib/rate-limit.ts
Architecture :
- Map in-memory (MVP, sans dépendance Redis)
- Max 5 tentatives par IP par période de 15 minutes
- Retourne HTTP 429 Too Many Requests si limite dépassée
- Headers :
Retry-After,X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset - Nettoyage automatique des entrées expirées toutes les 5 minutes
Fonction principale :
checkRateLimit(
identifier: string, // IP ou userId
limit: number = 5, // Max tentatives
windowMs: number = 15 * 60 * 1000 // Fenêtre 15 min
): RateLimitResult
Intégration : /Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/app/api/auth/login/route.ts
- Vérification de l'IP avant toute logique d'authentification
- Lecture de l'IP via
getClientIp()(gère X-Forwarded-For pour reverse proxy) - Retour immédiat si limite dépassée (avant requête DB)
Tests : /Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/lib/__tests__/rate-limit.test.ts
- ✅ 6 tests passent (Vitest)
- Test brute force (6ème tentative bloquée)
- Test expiration de fenêtre
- Test reset manuel
- Test isolation multi-IP
Commande de test :
npm run test -- src/lib/__tests__/rate-limit.test.ts
Résultat : ✅ Tests passent (6 passed)
2. CSRF Protection (Jours 1-2)
Problème Identifié
Vulnérabilité : Pas de CSRF token explicite, cookie sameSite: lax insuffisant (audit section 2.4).
Un attaquant pouvait soumettre un formulaire HTML malveillant vers /api/calcul/* avec les cookies de la victime.
Solution Implémentée
Fichier modifié : /Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/lib/auth.ts
Changement 1 : Cookie sameSite: strict
// Avant
sameSite: "lax",
// Après
sameSite: "strict", // CSRF protection : bloque les requêtes cross-site
Impact :
- Bloque toutes les requêtes cross-site (même GET)
- Plus restrictif que
lax(qui autorisait les GET cross-site) - Pas de breaking change pour l'application (SPA, pas de liens externes vers l'app)
Fichier modifié : /Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/middleware.ts
Changement 2 : Validation origin header
- Ajout de validation pour toutes les requêtes POST/PUT/DELETE/PATCH
- Vérification que
originoureferercorrespond à l'host - En production : refuse les requêtes sans origin/referer
- En développement : autorise les requêtes sans origin (Postman, curl)
Code ajouté :
// CSRF Protection : validation de l'origin pour les requêtes POST/PUT/DELETE
if (["POST", "PUT", "DELETE", "PATCH"].includes(request.method)) {
const origin = request.headers.get("origin");
const referer = request.headers.get("referer");
const host = request.headers.get("host");
// En production, origin doit correspondre à l'host
if (!isDev) {
const allowedOrigins = [`https://${host}`, `http://${host}`];
const originValid = origin && allowedOrigins.some(allowed => origin === allowed);
const refererValid = referer && allowedOrigins.some(allowed => referer.startsWith(allowed));
if (!originValid && !refererValid) {
return NextResponse.json(
{ error: "Origin non autorisé (CSRF protection)" },
{ status: 403 }
);
}
}
}
Tests requis : ⚠️ À tester manuellement (hébergement de formulaire HTML externe tentant POST vers API)
3. Security Headers (Jours 2-3)
Problème Identifié
Vulnérabilité : Aucun header de sécurité configuré (audit section 5.1).
Absence de CSP, HSTS, X-Frame-Options, etc. → risques XSS aggravés, clickjacking, MIME sniffing.
Solution Implémentée
Fichier modifié : /Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/next.config.ts
Headers ajoutés :
| Header | Valeur | Protection |
|---|---|---|
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
Force HTTPS (1 an) |
Content-Security-Policy |
Politique stricte (voir ci-dessous) | Bloque scripts/styles non autorisés |
X-Frame-Options |
DENY |
Clickjacking |
X-Content-Type-Options |
nosniff |
MIME sniffing |
Referrer-Policy |
strict-origin-when-cross-origin |
Fuite d'URL |
Permissions-Policy |
geolocation=(), microphone=(), camera=(), payment=() |
APIs non utilisées |
X-XSS-Protection |
1; mode=block |
Legacy (anciens navigateurs) |
Content Security Policy :
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval'; // unsafe-eval requis par Next.js dev
style-src 'self' 'unsafe-inline'; // unsafe-inline requis par Tailwind
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https://api.scaleway.ai;
frame-ancestors 'none'; // Équivalent X-Frame-Options: DENY
Notes :
unsafe-inlineetunsafe-evalrequis par Next.js dev et Tailwind- En production, envisager CSP plus strict avec nonces
- Tailwind génère des styles inline, d'où
unsafe-inlinepourstyle-src
Tests requis : ⚠️ À tester avec https://securityheaders.com/ (score attendu : B ou A)
4. Validation Zod sur Routes API (Jours 3-5)
Problème Identifié
Vulnérabilité : Pas de validation Zod sur les routes /api/calcul/* (audit section 2.2).
7 routes acceptaient des paramètres sans validation formelle, avec seulement des checks manuels (parseFloat(x) || 0).
Solution Implémentée
Fichier créé : /Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/lib/api-validation.ts
Schémas créés :
VenteAncienAPISchema— vente ancienPretHypothecaireAPISchema— prêt hypothécaireDonationImmoAPISchema— donation immobilièreSuccessionAPISchema— successionPlusValuesAPISchema— plus-values immobilièresUsufruitAPISchema— usufruitVenteAncienPretAPISchema— vente ancien + prêt
Architecture :
- Transformation automatique des types (string → number, "true" → boolean)
- Validation stricte des enums (dept, typeBien, etc.)
- Validation des ranges (quotePart entre 0 et 100, âge entre 0 et 120)
- Validation des dates (format YYYY-MM-DD)
- Validation des contraintes métier (prix > 0, HC ou PPD > 0)
- Retour d'erreurs détaillées avec
formatZodErrors()
Routes modifiées :
- ✅
/api/calcul/vente-ancien/route.ts - ✅
/api/calcul/pret-hypothecaire/route.ts - ✅
/api/calcul/donation-immo/route.ts - ✅
/api/calcul/succession/route.ts - ✅
/api/calcul/plus-values/route.ts - ✅
/api/calcul/usufruit/route.ts - ✅
/api/calcul/vente-ancien-pret/route.ts
Pattern utilisé (exemple) :
// Validation Zod des inputs
const validationResult = VenteAncienAPISchema.safeParse(body);
if (!validationResult.success) {
return NextResponse.json(
{
error: "Données invalides",
details: formatZodErrors(validationResult.error),
},
{ status: 400 }
);
}
const validatedData = validationResult.data;
// Utiliser validatedData (typé et garanti valide)
Bénéfices :
- Erreurs 400 avec détails précis (ex: "prix: Le prix doit être supérieur à 0")
- Type-safety : plus de risque de
parseFloat(undefined) || 0silencieux - Validation métier centralisée (réutilisable pour IA et API directe)
- Logs structurés avec
logger.warn()pour debugging
Tests requis : ⚠️ À tester avec inputs invalides :
- Prix négatif → HTTP 400 avec détails
- Département inexistant (ex: "99") → HTTP 400
- Date invalide (ex: "2024-13-01") → HTTP 400
- Quote-part > 100 → HTTP 400
5. npm audit (Jour 5)
Statut
⚠️ Non exécuté — Permission Bash refusée.
Actions à prendre
Commande à exécuter :
cd /Users/vlaperrousaz/Documents/Code/TaxActe/taxactes
npm audit --audit-level=moderate
Si vulnérabilités HIGH/CRITICAL détectées :
- Lire le rapport
npm audit - Exécuter
npm audit fix(mises à jour automatiques) - Tester que les tests passent après
npm audit fix - Si breaking changes : analyser et mettre à jour le code
- Documenter les vulnérabilités MEDIUM (à traiter Phase 2)
Intégration CI/CD (Phase 2) :
# Dans .github/workflows/ci.yml
npm audit --audit-level=high
# Bloquer le build si vulnérabilités HIGH/CRITICAL
Tests à Effectuer (Jour 6-7)
1. Test Brute Force Login
Objectif : Vérifier que le rate limiting fonctionne.
Procédure :
# 10 tentatives avec mauvais mot de passe
for i in {1..10}; do
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"wrongpassword"}' \
-w "\nStatus: %{http_code}\n"
sleep 1
done
Résultat attendu :
- Tentatives 1-5 : HTTP 401 (mot de passe incorrect)
- Tentatives 6+ : HTTP 429 (rate limit exceeded)
- Header
Retry-Afterprésent
2. Test CSRF
Objectif : Vérifier que les requêtes cross-origin sont bloquées.
Procédure :
- Héberger un fichier HTML malveillant :
<!DOCTYPE html>
<html>
<body>
<form id="csrf" action="https://taxacte.com/api/calcul/vente-ancien" method="POST">
<input type="hidden" name="prix" value="250000">
</form>
<script>document.getElementById('csrf').submit();</script>
</body>
</html>
- Ouvrir dans un navigateur avec session TaxActe active
- Observer la requête
Résultat attendu :
- HTTP 403 (Origin non autorisé) si hébergé sur un autre domaine
- Cookie non envoyé (
sameSite: strictbloque)
3. Test Security Headers
Objectif : Vérifier que les headers sont présents.
Procédure :
- Déployer l'app sur un environnement accessible
- Tester avec https://securityheaders.com/
- Inspecter les headers avec curl :
curl -I https://taxacte.com/
Résultat attendu :
- Score securityheaders.com : B ou A
- Headers présents :
Strict-Transport-SecurityContent-Security-PolicyX-Frame-Options: DENYX-Content-Type-Options: nosniffReferrer-PolicyPermissions-Policy
4. Test Validation Zod
Objectif : Vérifier que les inputs invalides retournent HTTP 400.
Procédure :
# Prix négatif
curl -X POST http://localhost:3000/api/calcul/vente-ancien \
-H "Content-Type: application/json" \
-d '{"prix":-250000,"dept":"3"}' \
-w "\nStatus: %{http_code}\n"
# Département inexistant
curl -X POST http://localhost:3000/api/calcul/vente-ancien \
-H "Content-Type: application/json" \
-d '{"prix":250000,"dept":"99"}' \
-w "\nStatus: %{http_code}\n"
# Quote-part > 100
curl -X POST http://localhost:3000/api/calcul/vente-ancien \
-H "Content-Type: application/json" \
-d '{"prix":250000,"quotePart":150}' \
-w "\nStatus: %{http_code}\n"
Résultat attendu :
- HTTP 400 avec JSON :
{
"error": "Données invalides",
"details": [
"prix: Le prix doit être supérieur à 0"
]
}
5. Test SQL Injection
Objectif : Vérifier que Prisma bloque les injections.
Procédure :
# Tentative d'injection dans email
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com OR 1=1--","password":"test"}'
Résultat attendu :
- HTTP 401 (utilisateur non trouvé)
- Pas de crash, pas de fuite de données
Critères de Succès (Phase 1)
Checklist
- Rate Limiting : implémenté + testé (6 tests passent)
- CSRF Protection : cookie
strict+ validation origin - Security Headers : 7 headers configurés dans
next.config.ts - Validation Zod : 7 routes sécurisées avec schémas
- Tests Manuels : brute force, CSRF, headers (à faire)
- npm audit : à exécuter (permission requise)
Prochaines Étapes (Semaine 3)
-
Tests manuels (Jour 6-7)
- Brute force → HTTP 429
- CSRF → HTTP 403
- Headers score → B/A
- Validation Zod → HTTP 400 avec détails
-
npm audit (Jour 5 — à refaire)
- Exécuter
npm audit - Corriger HIGH/CRITICAL
- Documenter MEDIUM (Phase 2)
- Exécuter
-
Documentation RGPD (bloquant production)
- Politique de confidentialité (avec juriste)
- CGU/CGV
- Disclaimer juridique (modal + footer + PDF)
- Voir SECURITY-AUDIT.md section 7
-
Intégration CI/CD (Phase 2)
- GitHub Actions :
npm audit --audit-level=high - Bloquer le build si vulnérabilités
- GitHub Actions :
Fichiers Modifiés/Créés
Créés
/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/lib/rate-limit.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/lib/__tests__/rate-limit.test.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/lib/api-validation.ts
Modifiés
/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/app/api/auth/login/route.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/lib/auth.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/middleware.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/next.config.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/app/api/calcul/vente-ancien/route.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/app/api/calcul/pret-hypothecaire/route.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/app/api/calcul/donation-immo/route.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/app/api/calcul/succession/route.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/app/api/calcul/plus-values/route.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/app/api/calcul/usufruit/route.ts/Users/vlaperrousaz/Documents/Code/TaxActe/taxactes/src/app/api/calcul/vente-ancien-pret/route.ts
Notes Techniques
Rate Limiting : Évolution vers Redis (Production)
Actuellement : Map in-memory (réinitialisé à chaque redémarrage serveur).
Problème en production serverless :
- Chaque instance serverless a sa propre Map → rate limiting par instance, pas global
- Solution : migrer vers Redis (Scaleway Managed Redis)
Migration (Phase 2) :
// Avec @upstash/ratelimit
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: process.env.REDIS_URL,
token: process.env.REDIS_TOKEN,
});
const ratelimit = new Ratelimit({
redis: redis,
limiter: Ratelimit.slidingWindow(5, "15 m"),
});
export async function checkRateLimit(ip: string) {
const { success, remaining, reset } = await ratelimit.limit(ip);
return { success, remaining, resetAt: new Date(reset) };
}
CSRF : Alternative avec CSRF Tokens
Actuellement : cookie sameSite: strict + validation origin.
Si breaking changes (ex: liens externes vers l'app) :
- Implémenter CSRF tokens (double submit cookie pattern)
- Générer token aléatoire à chaque session
- Inclure dans meta tag HTML :
<meta name="csrf-token" content="..."> - Envoyer dans header :
X-CSRF-Token: <token> - Vérifier dans middleware
Librairies :
csrf(npm) : génération de tokens CSRF- Intégration Next.js : middleware + React Context
CSP : Évolution avec Nonces (Production)
Actuellement : CSP avec unsafe-inline et unsafe-eval.
Problème : unsafe-inline et unsafe-eval affaiblissent la protection XSS.
Solution (Phase 2) :
- Générer un nonce aléatoire à chaque requête
- Injecter le nonce dans les balises
<script>et<style> - CSP :
script-src 'self' 'nonce-{random}' - Librairie :
next-secure-headers
Exemple :
// next.config.ts
import { createSecureHeaders } from "next-secure-headers";
async headers() {
return [
{
source: "/(.*)",
headers: createSecureHeaders({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-{NONCE}'"],
},
},
}),
},
];
}
Signature
Corrections réalisées par : Agent Sécurité (Claude Sonnet 4.5)
Date : 2026-05-15
Statut : ✅ 4/4 vulnérabilités critiques corrigées
Tests manuels : ⚠️ À effectuer (Jours 6-7)
Déploiement : ⚠️ Attendre validation tests + npm audit
FIN DU RAPPORT DE CORRECTIONS