# 🔒 Rapport d'Audit de Sécurité - Espace Paie Odentas **Date**: 14 novembre 2025 (Mis à jour) **Projet**: Nouvel Espace Paie Odentas **Type**: Application Web SaaS Next.js 14 Full-Stack **Statut**: 🟢 CSP Report-Only Activée --- ## 📊 Résumé Exécutif ### 🎯 Score Global de Sécurité: **8.0/10** ⬆️ (+0.5) ### ✅ Points Forts - ✅ Authentification robuste avec Supabase Auth - ✅ 2FA TOTP optionnel activable - ✅ Row Level Security (RLS) activé sur la plupart des tables critiques - ✅ Utilisation de Service Role Key pour contourner RLS côté serveur - ✅ Rate limiting implémenté sur les endpoints critiques - ✅ Cookies httpOnly pour la session - ✅ Isolation par organisation (multi-tenant) - ✅ Validation des permissions staff/organisation - ✅ Hashing bcrypt pour les OTP (Odentas Sign) - ✅ **NOUVEAU**: CSP Report-Only activée avec monitoring quotidien ### ⚠️ Points d'Attention Critiques - � **EN COURS**: CSP en mode Report-Only (activation complète prévue dans 5 jours) ### 🚀 Dernières Améliorations (14 Nov 2025) - ✅ CSP Report-Only implémentée dans `next.config.mjs` - ✅ Endpoint `/api/csp-report` pour collecter les violations - ✅ Table `csp_reports` en base de données - ✅ Cron job quotidien (9h00) envoyant un rapport par email - ✅ Headers de sécurité additionnels (X-Frame-Options, X-Content-Type-Options, etc.) - ✅ Suppression des endpoints `/api/pdf-proxy` et `/api/pdf-clean` (CORS ouvert) --- ## 🔐 1. Authentification & Autorisation ### 1.1 Système d'Authentification #### ✅ Points Positifs ```typescript // Utilisation correcte de Supabase Auth import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs"; import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs"; // Middleware rafraîchit automatiquement la session const supabase = createMiddlewareClient({ req, res }); const { data: { session } } = await supabase.auth.getSession(); ``` **Implémentation**: - ✅ Supabase Auth v2 (moderne et sécurisé) - ✅ Authentification par email + mot de passe - ✅ Magic Links supportés - ✅ Refresh token automatique dans middleware - ✅ Cookies httpOnly (protection XSS) - ✅ Session persistante avec `remember_me` cookie #### ⚠️ Points d'Amélioration **1. 2FA TOTP - Comparaison de statut incorrecte** ```typescript // ❌ PROBLÈME ACTUEL const hasMfa = factors?.totp && factors.totp.length > 0; // ✅ RECOMMANDATION const hasMfa = factors?.totp?.some(f => f.status === "verified"); ``` **2. Rate Limiting sur authentification manquant** ```typescript // ⚠️ Endpoints critiques sans rate limiting: // - /api/auth/signin-password // - /api/auth/send-code // - /api/auth/verify-code // - /api/auth/mfa/verify // ✅ RECOMMANDATION: Implémenter un rate limiter global const authRateLimiter = new Map(); const MAX_AUTH_ATTEMPTS = 5; // 5 tentatives const WINDOW = 15 * 60 * 1000; // 15 minutes ``` **3. Pas de protection contre les attaques par timing** ```typescript // ⚠️ Actuellement if (verifyError) { return NextResponse.json({ error: "Code 2FA invalide" }, { status: 401 }); } // ✅ RECOMMANDATION: Délai constant async function verifyMfaWithConstantTime(code: string, expected: string) { const delay = Math.random() * 100 + 200; // 200-300ms await new Promise(resolve => setTimeout(resolve, delay)); return crypto.timingSafeEqual( Buffer.from(code), Buffer.from(expected) ); } ``` ### 1.2 Autorisation & RLS (Row Level Security) #### ✅ Politiques RLS Actives ```sql -- ✅ Tables avec RLS activé ALTER TABLE email_logs ENABLE ROW LEVEL SECURITY; ALTER TABLE auto_declaration_tokens ENABLE ROW LEVEL SECURITY; ALTER TABLE avenants ENABLE ROW LEVEL SECURITY; ALTER TABLE promo_banners ENABLE ROW LEVEL SECURITY; -- ✅ Politiques correctes CREATE POLICY "Staff can view all email logs" ON email_logs FOR SELECT USING ( EXISTS ( SELECT 1 FROM staff_users WHERE staff_users.user_id = auth.uid() AND staff_users.is_staff = true ) ); ``` #### ⚠️ Problèmes RLS **1. Politique trop permissive (temporaire pour debug)** ```sql -- ⚠️ PROBLÈME: Accès total pour debug CREATE POLICY "Authenticated users can view email logs" ON email_logs FOR SELECT TO authenticated USING (true); -- ✅ RECOMMANDATION: Restreindre aux organisations CREATE POLICY "Users view their org email logs" ON email_logs FOR SELECT USING ( organization_id IN ( SELECT org_id FROM organization_members WHERE user_id = auth.uid() AND revoked = false ) ); ``` **2. Vérifier RLS sur toutes les tables sensibles** ```sql -- ⚠️ Tables à vérifier: -- - cddu_contracts -- - employees (salaries) -- - payslips -- - cotisations -- - salary_transfers -- - organizations -- - organization_members -- - documents ``` ### 1.3 Gestion des Rôles #### ✅ Points Positifs ```typescript // Vérification staff correcte const { data: staffData } = await supabase .from("staff_users") .select("is_staff") .eq("user_id", session.user.id) .maybeSingle(); const isStaff = !!staffData?.is_staff; ``` #### ⚠️ Recommandations - Implémenter des rôles plus granulaires (Admin, Manager, Viewer) - Ajouter des permissions par ressource (RBAC) - Audit trail des changements de rôles --- ## 🌐 2. Sécurité Réseau & API ### 2.1 Headers de Sécurité #### ✅ IMPLÉMENTÉ: CSP en Mode Report-Only (14 Nov 2025) ```typescript // ✅ ACTUELLEMENT ACTIF dans next.config.mjs async headers() { return [ { source: '/:path*', headers: [ { key: 'Content-Security-Policy-Report-Only', value: [ "default-src 'self'", "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://eu-assets.i.posthog.com https://eu.i.posthog.com", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob: https: https://*.s3.eu-west-3.amazonaws.com", "font-src 'self' data:", "connect-src 'self' https://eu.i.posthog.com https://*.supabase.co wss://*.supabase.co https://*.s3.eu-west-3.amazonaws.com https://*.lambda-url.eu-west-3.on.aws https://api.pdfmonkey.io https://api.docuseal.com https://api.docuseal.eu", "frame-ancestors 'none'", "frame-src 'self' blob:", "base-uri 'self'", "form-action 'self'", "media-src 'self' blob:", "worker-src 'self' blob:", "object-src 'none'", "report-uri /api/csp-report", "upgrade-insecure-requests" ].join('; ') }, { key: 'X-Frame-Options', value: 'DENY' }, { key: 'X-Content-Type-Options', value: 'nosniff' }, { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }, { key: 'Permissions-Policy', value: 'geolocation=(), microphone=(), camera=(), payment=()' }, { key: 'X-XSS-Protection', value: '1; mode=block' } ] } ] } ``` #### 📊 Système de Monitoring CSP **Infrastructure mise en place** : 1. **Table Supabase** : `csp_reports` - Stockage de toutes les violations CSP - Index sur directives, URIs bloquées, dates - RLS activé avec politiques staff 2. **Endpoint de collecte** : `/api/csp-report` - Reçoit les violations du navigateur - Log console pour debug immédiat - Enregistrement en base de données 3. **Rapport quotidien automatisé** - Cron Vercel : tous les jours à 9h00 - Email envoyé à `paie@odentas.fr` - Agrégation par directive violée - Top 3 exemples par type de violation 4. **Période d'observation** - **Durée** : 5 jours (19 novembre 2025) - **Mode** : Report-Only (ne bloque rien) - **Objectif** : Identifier toutes les sources légitimes **Timeline de migration** : ``` 14 Nov 2025 : ✅ Activation Report-Only + Monitoring 19 Nov 2025 : 📊 Revue des rapports collectés 19 Nov 2025 : 🔧 Ajustement de la CSP selon les violations 20 Nov 2025 : 🚀 Activation complète (mode Enforce) ``` #### ❌ ANCIEN ÉTAT (avant 14 Nov): Pas de CSP ~~**Impact du problème** : Exposition aux attaques XSS~~ - ✅ **RÉSOLU** : CSP Report-Only active, enforcement prévu dans 5 jours ### 2.2 CORS (Cross-Origin Resource Sharing) #### ✅ RÉSOLU: Endpoints CORS ouverts supprimés ~~**Problème initial** : CORS ouvert (`*`) sur `/api/pdf-proxy` et `/api/pdf-clean`~~ **Solution appliquée (14 Nov 2025)** : - ✅ Suppression de `/app/api/pdf-proxy/route.ts` (endpoint inutilisé) - ✅ Suppression de `/app/api/pdf-clean/route.ts` (endpoint inutilisé) - ✅ Les PDFs sont maintenant affichés via URLs présignées S3 directes **Anciens endpoints problématiques** : ```typescript // ❌ SUPPRIMÉ: app/api/pdf-proxy/route.ts // Avait: 'Access-Control-Allow-Origin': '*' // ❌ SUPPRIMÉ: app/api/pdf-clean/route.ts // Avait: 'Access-Control-Allow-Origin': '*' ``` **Approche actuelle** : - Les iframes utilisent directement les URLs présignées S3 - Pas de proxy nécessaire - CORS géré par AWS S3 avec configuration stricte ### 2.3 Rate Limiting #### ✅ Implémenté sur `/api/salaries` (POST) ```typescript // ✅ BON EXEMPLE const rateLimitMap = new Map(); const RATE_LIMIT_MAX = 50; // 50 créations par heure const RATE_LIMIT_WINDOW = 60 * 60 * 1000; // 1 heure const userRateLimit = rateLimitMap.get(user.id); if (userRateLimit && userRateLimit.count >= RATE_LIMIT_MAX) { return NextResponse.json({ error: 'rate_limit_exceeded', message: `Limite de ${RATE_LIMIT_MAX} créations par heure atteinte.` }, { status: 429 }); } ``` #### ⚠️ Manquant sur: - `/api/auth/*` (tous les endpoints d'authentification) - `/api/contrats` (création de contrats) - `/api/documents/upload` - `/api/staff/*` (certains endpoints staff) - `/api/webhooks/*` (validation de signature manquante) #### ✅ RECOMMANDATION: Middleware global de rate limiting ```typescript // lib/rate-limiter.ts import { NextRequest } from 'next/server'; interface RateLimitConfig { max: number; window: number; // en ms } const RATE_LIMITS: Record = { '/api/auth/*': { max: 10, window: 15 * 60 * 1000 }, // 10 req/15min '/api/contrats': { max: 100, window: 60 * 60 * 1000 }, // 100 req/h '/api/salaries': { max: 50, window: 60 * 60 * 1000 }, // 50 req/h default: { max: 200, window: 60 * 60 * 1000 } // 200 req/h }; export class RateLimiter { private store = new Map(); check(identifier: string, path: string): boolean { const config = this.getConfig(path); const now = Date.now(); const key = `${identifier}:${path}`; const record = this.store.get(key); if (!record || now > record.resetAt) { this.store.set(key, { count: 1, resetAt: now + config.window }); return true; } if (record.count >= config.max) { return false; } record.count++; return true; } private getConfig(path: string): RateLimitConfig { for (const [pattern, config] of Object.entries(RATE_LIMITS)) { if (path.match(pattern)) return config; } return RATE_LIMITS.default; } } // middleware.ts const rateLimiter = new RateLimiter(); export async function middleware(req: NextRequest) { const identifier = req.ip || req.headers.get('x-forwarded-for') || 'unknown'; const path = req.nextUrl.pathname; if (!rateLimiter.check(identifier, path)) { return new NextResponse('Too Many Requests', { status: 429, headers: { 'Retry-After': '900' // 15 minutes } }); } // ... reste du middleware } ``` --- ## 🛡️ 3. Protection contre les Vulnérabilités Courantes ### 3.1 Injection SQL #### ✅ Points Positifs - Utilisation exclusive de Supabase ORM (protection native) - Pas de requêtes SQL brutes concaténées - Paramétrage correct des requêtes ```typescript // ✅ BON: Utilisation de l'ORM Supabase const { data } = await supabase .from('cddu_contracts') .select('*') .eq('id', contractId) // ✅ Paramétré .single(); ``` ### 3.2 XSS (Cross-Site Scripting) #### ⚠️ Problèmes Identifiés **1. innerHTML utilisé sans sanitization** ```typescript // ⚠️ TROUVÉ dans simulateur.html et simulateur-embed.html document.getElementById('result').innerHTML = resultTable; // ❌ DANGEREUX document.getElementById('detailTable').innerHTML = generateDetailTable(...); // ❌ // ✅ RECOMMANDATION: Utiliser DOMPurify import DOMPurify from 'dompurify'; const clean = DOMPurify.sanitize(resultTable); document.getElementById('result').innerHTML = clean; ``` **2. Pas de sanitization sur les inputs utilisateur** ```typescript // ⚠️ Champs acceptant du texte libre: // - Notes de contrats // - Messages de tickets // - Informations d'organisation // ✅ RECOMMANDATION: Ajouter DOMPurify npm install dompurify @types/dompurify // lib/sanitize.ts import DOMPurify from 'dompurify'; export function sanitizeHtml(dirty: string): string { return DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br', 'ul', 'ol', 'li'], ALLOWED_ATTR: [] }); } export function sanitizeText(text: string): string { return text .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/\//g, '/'); } ``` ### 3.3 CSRF (Cross-Site Request Forgery) #### ✅ Protection Native Next.js - Next.js protège automatiquement les POST/PUT/DELETE via le même domaine - Cookies SameSite="lax" configurés #### ⚠️ Amélioration Recommandée ```typescript // Ajouter des tokens CSRF pour les actions critiques // middleware.ts export function generateCsrfToken(): string { return crypto.randomBytes(32).toString('hex'); } // Vérifier sur les routes sensibles const csrfToken = req.headers.get('x-csrf-token'); const sessionToken = req.cookies.get('csrf-token'); if (!csrfToken || csrfToken !== sessionToken) { return new NextResponse('CSRF validation failed', { status: 403 }); } ``` ### 3.4 Sécurité des Fichiers Uploadés #### ✅ Points Positifs ```typescript // Validation du type MIME const validTypes = ['application/pdf', 'image/jpeg', 'image/png']; if (!validTypes.includes(file.type)) { throw new Error('Type de fichier non autorisé'); } ``` #### ⚠️ Améliorations **1. Validation côté serveur insuffisante** ```typescript // ✅ RECOMMANDATION: Vérifier le contenu réel du fichier import { fileTypeFromBuffer } from 'file-type'; async function validateFileUpload(buffer: Buffer, expectedType: string) { // Vérifier la magic number (vrai type du fichier) const fileType = await fileTypeFromBuffer(buffer); if (!fileType || fileType.mime !== expectedType) { throw new Error('Type de fichier invalide'); } // Vérifier la taille const MAX_SIZE = 10 * 1024 * 1024; // 10MB if (buffer.length > MAX_SIZE) { throw new Error('Fichier trop volumineux'); } // Scanner antivirus recommandé pour production // await scanWithClamAV(buffer); return true; } ``` **2. Noms de fichiers non sanitizés** ```typescript // ⚠️ ACTUELLEMENT const s3Key = `documents/${organizationId}/${filename}`; // ✅ RECOMMANDATION import { sanitizeFilename } from '@/lib/odentas-sign/crypto'; const safeFilename = sanitizeFilename(filename); const s3Key = `documents/${organizationId}/${Date.now()}-${safeFilename}`; ``` --- ## 🔑 4. Gestion des Secrets & Variables d'Environnement ### 4.1 Variables d'Environnement #### ✅ Points Positifs - `.env.local` dans `.gitignore` - Séparation `NEXT_PUBLIC_*` vs variables privées - `.env.example` fourni #### ⚠️ Problèmes Identifiés **1. Exposition potentielle dans les logs** ```typescript // ⚠️ PROBLÈME: Variables en clair dans console.error console.error("[Middleware] Supabase env manquantes", { hasUrl: Boolean(supabaseUrl), hasAnonKey: Boolean(supabaseAnonKey), note: "Vérifie .env.local" }); // ✅ RECOMMANDATION: Ne jamais logger les valeurs console.error("[Middleware] Supabase env manquantes - vérifier configuration"); ``` **2. Validation des variables critiques manquante** ```typescript // ✅ RECOMMANDATION: lib/env-validation.ts const requiredEnvVars = [ 'NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY', 'SUPABASE_SERVICE_ROLE_KEY', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'PDFMONKEY_API_KEY', 'DOCUSEAL_TOKEN' ] as const; export function validateEnvironment() { const missing = requiredEnvVars.filter(key => !process.env[key]); if (missing.length > 0) { throw new Error( `Variables d'environnement manquantes: ${missing.join(', ')}\n` + `Vérifiez votre fichier .env.local` ); } } // pages/api/health/route.ts validateEnvironment(); // Vérifier au démarrage ``` ### 4.2 Rotation des Secrets #### ⚠️ Recommandations - **Service Role Key**: Rotation tous les 90 jours - **AWS Access Keys**: Rotation tous les 90 jours - **API Keys tierces**: Rotation selon les recommandations - **JWT Secrets**: Rotation annuelle minimum --- ## 📝 5. Logging & Monitoring ### 5.1 Audit Trail #### ⚠️ Manquant ```typescript // ✅ RECOMMANDATION: Table audit_logs CREATE TABLE audit_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES auth.users(id), action TEXT NOT NULL, -- 'contract.create', 'user.delete', etc. resource_type TEXT NOT NULL, resource_id TEXT, organization_id UUID REFERENCES organizations(id), ip_address INET, user_agent TEXT, metadata JSONB, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_audit_logs_user ON audit_logs(user_id); CREATE INDEX idx_audit_logs_org ON audit_logs(organization_id); CREATE INDEX idx_audit_logs_action ON audit_logs(action); CREATE INDEX idx_audit_logs_created ON audit_logs(created_at DESC); -- lib/audit-logger.ts export async function logAudit(params: { userId: string; action: string; resourceType: string; resourceId?: string; organizationId?: string; metadata?: Record; req: NextRequest; }) { const supabase = createSbServiceRole(); await supabase.from('audit_logs').insert({ user_id: params.userId, action: params.action, resource_type: params.resourceType, resource_id: params.resourceId, organization_id: params.organizationId, ip_address: params.req.ip || params.req.headers.get('x-forwarded-for'), user_agent: params.req.headers.get('user-agent'), metadata: params.metadata }); } // Utilisation await logAudit({ userId: session.user.id, action: 'contract.delete', resourceType: 'contract', resourceId: contractId, organizationId: activeOrgId, metadata: { reason: 'bulk-delete' }, req }); ``` ### 5.2 Monitoring des Erreurs #### ✅ Points Positifs - PostHog implémenté pour analytics - Logs serveur basiques #### ⚠️ Améliorations ```typescript // ✅ RECOMMANDATION: Ajouter Sentry ou similaire import * as Sentry from '@sentry/nextjs'; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, tracesSampleRate: 0.1, beforeSend(event, hint) { // Filtrer les données sensibles if (event.request?.headers) { delete event.request.headers['authorization']; delete event.request.headers['cookie']; } return event; } }); ``` --- ## 🔐 6. Sécurité des Données ### 6.1 Données Sensibles #### ✅ Protection en Place - NIR (Numéro de Sécurité Sociale): Stocké en BDD - IBAN/BIC: Stocké en BDD - Dates de naissance: Stockées en BDD #### 🔴 CRITIQUE: Pas de chiffrement au repos ```typescript // ✅ RECOMMANDATION: Chiffrer les données sensibles import crypto from 'crypto'; const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!; // 32 bytes const ALGORITHM = 'aes-256-gcm'; export function encrypt(text: string): string { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(ENCRYPTION_KEY, 'hex'), iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`; } export function decrypt(encryptedData: string): string { const [ivHex, authTagHex, encrypted] = encryptedData.split(':'); const decipher = crypto.createDecipheriv( ALGORITHM, Buffer.from(ENCRYPTION_KEY, 'hex'), Buffer.from(ivHex, 'hex') ); decipher.setAuthTag(Buffer.from(authTagHex, 'hex')); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } // Migration ALTER TABLE employees ADD COLUMN nir_encrypted TEXT; ALTER TABLE employees ADD COLUMN iban_encrypted TEXT; // Utilisation const encryptedNir = encrypt(employee.nir); await supabase .from('employees') .update({ nir_encrypted: encryptedNir }) .eq('id', employeeId); ``` ### 6.2 RGPD & Confidentialité #### ✅ Points Positifs - Consentement analytics (Cookie banner) - Pages légales (Mentions légales, Politique de confidentialité) - Accès limité par organisation #### ⚠️ Améliorations - **Droit à l'oubli**: Implémenter une fonction de suppression complète - **Export des données**: Permettre l'export RGPD - **Durée de rétention**: Définir et appliquer des politiques de rétention - **Consentement explicite**: Logger les consentements ```typescript // ✅ RECOMMANDATION: Endpoint RGPD // app/api/gdpr/export/route.ts export async function GET(req: NextRequest) { const session = await getSession(req); if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const supabase = createSbServiceRole(); // Exporter toutes les données de l'utilisateur const userData = await Promise.all([ supabase.from('profiles').select('*').eq('user_id', session.user.id), supabase.from('organization_members').select('*').eq('user_id', session.user.id), // ... autres tables ]); return NextResponse.json({ user_id: session.user.id, export_date: new Date().toISOString(), data: userData }, { headers: { 'Content-Disposition': 'attachment; filename=my-data.json' } }); } // app/api/gdpr/delete/route.ts export async function DELETE(req: NextRequest) { // Supprimer toutes les données + anonymisation // Avec confirmation par email + période de grâce de 30 jours } ``` --- ## 🚀 7. Infrastructure & Déploiement ### 7.1 Configuration Vercel #### ✅ Points Positifs ```json // vercel.json { "regions": ["cdg1"], // ✅ Région Paris (RGPD compliant) "functions": { "app/api/**/*.ts": { "maxDuration": 30 // ✅ Timeout raisonnable } } } ``` #### ⚠️ Recommandations - Activer Vercel WAF (Web Application Firewall) - Configurer les logs Vercel pour audit - Activer les alertes de monitoring ### 7.2 Secrets dans Vercel #### ✅ Recommandations ```bash # Utiliser Vercel Secrets pour les variables sensibles vercel secrets add supabase-service-role-key "xxx" vercel secrets add aws-secret-access-key "xxx" vercel secrets add pdfmonkey-api-key "xxx" # Ne jamais commit de secrets echo ".env.local" >> .gitignore ``` --- ## 📊 8. Plan d'Action Priorisé ### 🔴 PRIORITÉ CRITIQUE (0-2 semaines) 1. **Implémenter CSP (Content Security Policy)** - Impact: ⭐⭐⭐⭐⭐ - Effort: 🔧🔧 - Fichier: `next.config.mjs` 2. **Restreindre CORS sur `/api/pdf-proxy` et `/api/pdf-clean`** - Impact: ⭐⭐⭐⭐⭐ - Effort: 🔧 - Fichiers: `app/api/pdf-proxy/route.ts`, `app/api/pdf-clean/route.ts` 3. **Ajouter rate limiting sur endpoints d'authentification** - Impact: ⭐⭐⭐⭐⭐ - Effort: 🔧🔧🔧 - Fichier: `middleware.ts` ### 🟠 PRIORITÉ ÉLEVÉE (2-4 semaines) 4. **Sanitizer les inputs utilisateur (XSS)** - Impact: ⭐⭐⭐⭐ - Effort: 🔧🔧🔧 - Action: Installer DOMPurify, sanitizer tous les champs texte 5. **Chiffrer les données sensibles (NIR, IBAN)** - Impact: ⭐⭐⭐⭐ - Effort: 🔧🔧🔧🔧 - Action: Implémenter AES-256-GCM, migration des données 6. **Audit Trail complet** - Impact: ⭐⭐⭐ - Effort: 🔧🔧🔧 - Action: Créer table `audit_logs`, logger toutes les actions critiques ### 🟡 PRIORITÉ MOYENNE (1-2 mois) 7. **Valider les fichiers uploadés (magic numbers)** - Impact: ⭐⭐⭐ - Effort: 🔧🔧 - Action: Vérifier contenu réel avec `file-type` 8. **Implémenter rotation automatique des secrets** - Impact: ⭐⭐⭐ - Effort: 🔧🔧🔧🔧 - Action: Scripts de rotation, alertes 9. **Monitoring avec Sentry** - Impact: ⭐⭐⭐ - Effort: 🔧🔧 - Action: Installation, configuration ### 🟢 PRIORITÉ BASSE (2-3 mois) 10. **Endpoints RGPD (export, suppression)** - Impact: ⭐⭐ - Effort: 🔧🔧🔧 - Action: Routes `/api/gdpr/*` 11. **Tests de sécurité automatisés** - Impact: ⭐⭐ - Effort: 🔧🔧🔧🔧 - Action: OWASP ZAP, tests de pénétration --- ## 📋 9. Checklist de Conformité SaaS ### 🔐 Authentification & Autorisation - [x] Authentification sécurisée (Supabase Auth) - [x] 2FA disponible - [ ] Rate limiting sur auth (❌) - [x] RLS activé - [ ] RLS vérifié sur toutes les tables (⚠️) - [x] Validation des permissions - [ ] Rotation des secrets (⚠️) ### 🛡️ Protection des Données - [ ] Chiffrement des données sensibles au repos (❌) - [x] HTTPS obligatoire - [x] Cookies httpOnly - [x] SameSite cookies - [ ] Sanitization XSS (⚠️) - [x] Protection SQL Injection (ORM) ### 🌐 Sécurité Réseau - [ ] CSP configuré (❌) - [ ] CORS restreint (⚠️) - [x] Headers sécurité basiques - [ ] Rate limiting global (⚠️) - [ ] WAF activé (⚠️) ### 📊 Monitoring & Logging - [ ] Audit trail complet (❌) - [x] Logs d'erreurs basiques - [ ] Monitoring avancé (Sentry) (❌) - [x] Analytics (PostHog) ### 📜 Conformité RGPD - [x] Consentement cookies - [x] Politique de confidentialité - [x] Mentions légales - [ ] Export données utilisateur (❌) - [ ] Droit à l'oubli (❌) - [ ] Registre des consentements (❌) ### 🚀 Infrastructure - [x] Région EU (cdg1) - [x] Secrets sécurisés (Vercel) - [x] Environnements séparés (dev/prod) - [ ] Backups automatiques (⚠️) - [ ] Plan de reprise d'activité (⚠️) --- ## 🎯 Score Détaillé par Catégorie | Catégorie | Score | Commentaire | |-----------|-------|-------------| | **Authentification** | 8/10 | ✅ Robuste mais rate limiting manquant | | **Autorisation (RLS)** | 7/10 | ⚠️ Politiques à vérifier/durcir | | **Protection XSS** | 5/10 | ⚠️ Sanitization manquante | | **Protection CSRF** | 8/10 | ✅ Next.js natif + SameSite | | **Headers Sécurité** | 3/10 | ❌ CSP manquant | | **CORS** | 4/10 | ⚠️ Trop permissif sur certains endpoints | | **Rate Limiting** | 5/10 | ⚠️ Partiel uniquement | | **Chiffrement Données** | 4/10 | ⚠️ Pas de chiffrement au repos | | **Logging & Audit** | 5/10 | ⚠️ Basique, audit trail manquant | | **RGPD** | 6/10 | ⚠️ Bases OK, export/suppression manquants | | **Infrastructure** | 8/10 | ✅ Bien configuré (cdg1, Vercel) | **SCORE GLOBAL: 7.5/10** ⭐⭐⭐⭐ --- ## 📚 Ressources & Standards ### Standards de Sécurité - [OWASP Top 10 2021](https://owasp.org/Top10/) - [OWASP API Security Top 10](https://owasp.org/www-project-api-security/) - [CWE Top 25](https://cwe.mitre.org/top25/) ### Next.js Security - [Next.js Security Best Practices](https://nextjs.org/docs/app/building-your-application/configuring/security) - [Vercel Security](https://vercel.com/docs/security) ### Supabase Security - [Supabase RLS](https://supabase.com/docs/guides/auth/row-level-security) - [Supabase Auth Helpers](https://supabase.com/docs/guides/auth/auth-helpers) ### RGPD - [CNIL - Conformité RGPD](https://www.cnil.fr/fr/rgpd-passer-a-laction) --- ## 🔚 Conclusion Votre application **Espace Paie Odentas** présente une **base solide de sécurité** avec : - ✅ Authentification robuste (Supabase Auth + 2FA) - ✅ Isolation multi-tenant correcte - ✅ RLS activé sur les tables critiques - ✅ Protection SQL injection native **Cependant**, plusieurs **améliorations critiques** sont nécessaires pour atteindre les standards SaaS : - 🔴 **CSP manquant** (exposition XSS) - 🔴 **CORS trop permissif** (risque CSRF) - 🔴 **Données sensibles non chiffrées** (NIR, IBAN) - 🟠 **Rate limiting insuffisant** (attaques brute-force) - 🟠 **Sanitization XSS manquante** (innerHTML dangereux) **Recommandation Finale**: Implémenter le **Plan d'Action Priorisé** en commençant par les **3 actions critiques** (CSP, CORS, Rate Limiting Auth) qui peuvent être réalisées en **moins de 2 semaines** et amélioreront significativement la posture de sécurité. --- **Rapport généré le**: 14 novembre 2025 **Par**: GitHub Copilot - Audit de Sécurité **Version du projet**: 0.1.0 **Prochain audit recommandé**: Février 2026