From 0a816fa881ab51497976392249dbb3ed36775eaf Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 16:21:40 +0000 Subject: [PATCH] fix(securite): lock down the two unauthenticated emergency endpoints - POST /api/emergency-admin-reset accepted a fallback secret hardcoded in the repository, letting anyone who read the source reset the production admin account to admin/admin (EMERGENCY_SECRET is not set in the shipped docker-compose, so the fallback was live). The route now returns 404 unless EMERGENCY_SECRET is explicitly configured, and invalid attempts are logged. - POST /api/admin/emergency-migration ran database migrations with no authentication at all; it now requires an authenticated admin. A sweep of the remaining API surface found no other unauthenticated mutation or read routes beyond /api/health. .env.example documents EMERGENCY_SECRET and ENCRYPTION_KEY. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe --- .env.example | 10 +++++++++- server/index.production.ts | 17 ++++++++++++----- server/routes.ts | 8 +++++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 82a1cea..2a01028 100644 --- a/.env.example +++ b/.env.example @@ -21,4 +21,12 @@ POSTGRES_PORT=5434 # Note de sécurité: # Les identifiants sont préconfigurés pour simplifier le déploiement. -# En production, vous pouvez les modifier si nécessaire. \ No newline at end of file +# En production, vous pouvez les modifier si nécessaire. +# Chiffrement des secrets en base (mots de passe SMTP, jetons NocoDB). +# Repli sur SESSION_SECRET si absent. ATTENTION : changer cette clé rend +# les secrets déjà chiffrés illisibles. +ENCRYPTION_KEY= + +# Route de secours POST /api/emergency-admin-reset : désactivée si vide. +# Ne définir qu'en cas de besoin, puis retirer. +EMERGENCY_SECRET= diff --git a/server/index.production.ts b/server/index.production.ts index 2960487..1127e1f 100644 --- a/server/index.production.ts +++ b/server/index.production.ts @@ -74,14 +74,21 @@ async function registerProductionRoutes(app: Express): Promise { }); }); - // Emergency admin reset endpoint (production only) + // Emergency admin reset endpoint (production only) + // SÉCURITÉ : n'existe que si EMERGENCY_SECRET est défini dans l'environnement. + // L'ancien secret par défaut était codé en dur dans ce fichier, donc lisible + // par quiconque accède au dépôt — n'importe qui pouvait réinitialiser le + // compte admin en production. app.post('/api/emergency-admin-reset', async (req: Request, res: Response) => { try { + const emergencySecret = process.env.EMERGENCY_SECRET; + if (!emergencySecret) { + return res.status(404).json({ error: 'Not found' }); + } + const { secret } = req.body; - - // Require emergency secret - const emergencySecret = process.env.EMERGENCY_SECRET || 'logiflow-admin-reset-2025'; - if (secret !== emergencySecret) { + if (!secret || secret !== emergencySecret) { + console.warn('🚨 Tentative de reset admin avec un secret invalide'); return res.status(403).json({ error: 'Invalid emergency secret' }); } diff --git a/server/routes.ts b/server/routes.ts index 43f95fe..a1382d8 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -5558,8 +5558,14 @@ RÉSUMÉ DU SCAN }); // Emergency migration route for SAV priority column - app.post('/api/admin/emergency-migration', async (req, res) => { + // SÉCURITÉ : réservée aux administrateurs authentifiés (était accessible sans login) + app.post('/api/admin/emergency-migration', isAuthenticated, async (req: any, res) => { try { + const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); + if (!user || user.role !== 'admin') { + return res.status(403).json({ message: "Insufficient permissions" }); + } + console.log('🚨 EMERGENCY: Forcing SAV migration execution...'); // Import migration function