Comment fonctionne cette vérification
Scanaris regarde la réponse principale de ta page et vérifie deux conditions à la fois. Première : la réponse pose au moins un cookie (Set-Cookie) dont le nom commence par sess, sid, auth, token, jwt, connect.sid, phpsessid ou asp.net, c'est-à-dire un cookie qui identifie selon toute probabilité une session. Seconde : la même réponse porte Cache-Control avec la directive public. Seulement si les deux sont réunies, il ouvre un constat de sévérité moyenne avec le nom du cookie et la valeur de l'en-tête. C'est volontairement strict : un cookie de préférences avec cache public, ou un cookie de session sans public, ne sont pas signalés.
Pourquoi c'est important
Cache-Control: public est une instruction explicite à tout cache partagé (un CDN, un proxy inverse comme Varnish ou nginx, le proxy d'une entreprise ou d'un opérateur) indiquant que cette réponse peut être stockée et servie à d'autres utilisateurs. Si cette même réponse inclut un Set-Cookie de session, le cache peut stocker le cookie avec le corps et le donner au visiteur suivant. Ce visiteur entre avec la session du précédent, sans mot de passe, et voit ses données. C'est le genre de faille qu'on découvre quand un client écrit pour dire qu'il « est entré dans le compte de quelqu'un d'autre ». C'est arrivé à des banques et à de grands commerçants, toujours par la même combinaison : du contenu personnalisé marqué comme cachable.
D'où ça vient
Généralement d'une règle de cache trop large : un add_header Cache-Control "public, max-age=3600" dans nginx appliqué à toutes les routes, y compris celles qui créent une session ; une règle « Cache Everything » dans Cloudflare ; ou un routeRules avec des en-têtes de cache pour '/**' dans une appli qui pose des cookies sur la page principale. La plupart des CDN refusent de stocker les réponses avec Set-Cookie par défaut, et nginx aussi sauf si tu lui dis d'ignorer cet en-tête, mais un public explicite, c'est toi qui leur dis de le faire.
Comment corriger
- Les réponses qui créent ou renouvellent une session portent
Cache-Control: private, no-store. C'est ce que font par défautexpress-session, Laravel, Django et Rails ; si tu voispublic, une couche ultérieure l'a ajouté. - Sépare les règles de cache par type de route.
publicavec unmax-agelong pour les fichiers statiques avec hash dans le nom (/_nuxt/,/_next/static/,/assets/) ;privateouno-storepour le HTML qui dépend de l'utilisateur. Dans nginx, utilise des blocslocationdistincts ; dans Nuxt et Next.js, des règles par route plutôt qu'une règle globale. - Ne pose pas le cookie de session sur des pages publiques cachables. Si l'accueil n'a pas besoin de session, il ne doit pas en créer : crée le cookie à la connexion ou à l'entrée dans le tableau de bord.
- Si tu utilises un CDN avec « Cache Everything » ou équivalent, ajoute une règle pour ne pas cacher les réponses avec
Set-Cookieni les routes authentifiées. Et ajouteVary: Cookiecomme filet supplémentaire, sans que ça remplace ce qui précède.
Vérifie que c'est corrigé
curl -sI https://tondomaine.com | grep -iE "^(set-cookie|cache-control|vary):"
curl -sI https://tondomaine.com/dashboard | grep -iE "^(set-cookie|cache-control):"
S'il y a un Set-Cookie avec un nom de session, le Cache-Control de cette réponse doit être private, no-store ou no-cache, jamais public. Fais la seconde requête avec et sans ton cookie de session : la réponse authentifiée ne doit jamais apparaître sans lui.
Questions fréquentes
Mon CDN dit qu'il ne cache pas les réponses avec cookies. C'est donc un faux positif ?
La fuite ne se produit probablement pas aujourd'hui, mais tu dépends d'un comportement par défaut qu'une nouvelle règle ou un changement de fournisseur peuvent désactiver. L'en-tête dit que c'est public ; corrige l'en-tête et cesse de dépendre de l'exception.
Et les cookies d'analyse ou de consentement ?
Ils ne sont pas signalés : seuls les noms qui indiquent une session comptent. Un cookie de consentement sur une page publique en cache est normal. La façon dont les cookies de session doivent être configurés (Secure, HttpOnly, SameSite) est couverte par cookies non sécurisés.
Ma session est un jeton dans localStorage, pas un cookie.
Alors cette vérification ne s'applique pas, mais jeton dans le stockage si : un jeton accessible depuis JavaScript a d'autres problèmes.