1. MIME spoofing (upload) — app/api/admin/upload/route.ts
- Ajout de la validation par magic bytes : lit les premiers octets du
fichier et vérifie la signature binaire réelle (JPEG FF D8 FF,
PNG 89 50 4E 47, GIF 47 49 46 38, WebP RIFF+WEBP, AVIF ftyp box)
- Extension dérivée exclusivement du MIME validé côté serveur
(MIME_TO_EXT), jamais du nom de fichier fourni par le client
- Un fichier .exe renommé en .jpg est désormais rejeté
2. Générateur de mot de passe non-cryptographique — stripe/webhook/route.ts
- Remplace Math.random() (non-déterministe mais prévisible) par
crypto.getRandomValues() (CSPRNG, conforme Web Crypto API)
3. Headers HTTP de sécurité manquants — middleware.ts (nouveau)
- X-Content-Type-Options: nosniff (anti MIME-sniffing navigateur)
- X-Frame-Options: SAMEORIGIN (anti clickjacking)
- Referrer-Policy: strict-origin-when-cross-origin
- Permissions-Policy: désactive camera, micro, geolocation
- Content-Security-Policy: whitelist stricte par type de ressource
(scripts, styles, images Unsplash/Supabase/Sanity, connect Supabase/Stripe,
frames Stripe uniquement, object-src none, form-action self)
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
80 lines
3.7 KiB
TypeScript
80 lines
3.7 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import type { NextRequest } from "next/server";
|
|
|
|
export function middleware(request: NextRequest) {
|
|
const response = NextResponse.next();
|
|
|
|
// ── X-Content-Type-Options ─────────────────────────────────────────────────
|
|
// Empêche le navigateur de "deviner" le Content-Type (MIME-sniffing).
|
|
// Sans ce header, un fichier .jpg contenant du HTML pourrait être exécuté.
|
|
response.headers.set("X-Content-Type-Options", "nosniff");
|
|
|
|
// ── X-Frame-Options ────────────────────────────────────────────────────────
|
|
// Bloque l'intégration de la page dans une iframe externe (clickjacking).
|
|
response.headers.set("X-Frame-Options", "SAMEORIGIN");
|
|
|
|
// ── Referrer-Policy ────────────────────────────────────────────────────────
|
|
// Limite les infos envoyées dans le header Referer aux requêtes cross-origin.
|
|
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
|
|
// ── Permissions-Policy ─────────────────────────────────────────────────────
|
|
// Désactive les APIs navigateur sensibles non utilisées par le site.
|
|
response.headers.set(
|
|
"Permissions-Policy",
|
|
"camera=(), microphone=(), geolocation=(), payment=(self)"
|
|
);
|
|
|
|
// ── Content-Security-Policy ────────────────────────────────────────────────
|
|
// Whitelist explicite des origines autorisées pour chaque type de ressource.
|
|
const supabaseHost = process.env.NEXT_PUBLIC_SUPABASE_URL
|
|
? new URL(process.env.NEXT_PUBLIC_SUPABASE_URL).host
|
|
: "*.supabase.co";
|
|
|
|
const csp = [
|
|
"default-src 'self'",
|
|
|
|
// Next.js 15 (App Router + RSC) nécessite 'unsafe-inline' et 'unsafe-eval'
|
|
// pour le bundle client et l'hydration côté navigateur.
|
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com https://m.stripe.com",
|
|
|
|
// Tailwind CSS + styles inline de composants React
|
|
"style-src 'self' 'unsafe-inline'",
|
|
|
|
// Images : self, data URIs (placeholders SVG), blob (prévisualisations upload),
|
|
// Unsplash (images par défaut), Supabase Storage (signed URLs), Sanity CDN
|
|
`img-src 'self' data: blob: https://images.unsplash.com https://${supabaseHost} https://cdn.sanity.io`,
|
|
|
|
// API calls : Supabase (REST + WebSocket realtime), Stripe
|
|
`connect-src 'self' https://${supabaseHost} wss://${supabaseHost} https://api.stripe.com https://r.stripe.com`,
|
|
|
|
// Polices web : uniquement self (pas de Google Fonts)
|
|
"font-src 'self'",
|
|
|
|
// Iframes : uniquement Stripe (paiement sécurisé)
|
|
"frame-src https://js.stripe.com",
|
|
|
|
// Aucun plugin (Flash, Java, etc.)
|
|
"object-src 'none'",
|
|
|
|
// Empêche l'injection de <base> qui pourrait détourner les URLs relatives
|
|
"base-uri 'self'",
|
|
|
|
// Les formulaires ne peuvent soumettre qu'à l'origine actuelle
|
|
"form-action 'self'",
|
|
|
|
// Force HTTPS pour toutes les sous-ressources (utile si déployé en HTTP accidentellement)
|
|
"upgrade-insecure-requests",
|
|
].join("; ");
|
|
|
|
response.headers.set("Content-Security-Policy", csp);
|
|
|
|
return response;
|
|
}
|
|
|
|
export const config = {
|
|
// Appliquer sur toutes les routes sauf les assets statiques Next.js
|
|
matcher: [
|
|
"/((?!_next/static|_next/image|favicon\\.ico|.*\\.svg|.*\\.ico|.*\\.png|.*\\.jpg|.*\\.jpeg|.*\\.webp|.*\\.gif).*)",
|
|
],
|
|
};
|