Cómo funciona esta comprobación
Scanaris mira la respuesta principal de tu página y comprueba dos condiciones a la vez. Primera: la respuesta establece al menos una cookie (Set-Cookie) cuyo nombre empieza por sess, sid, auth, token, jwt, connect.sid, phpsessid o asp.net, es decir, una cookie que con toda probabilidad identifica una sesión. Segunda: la misma respuesta lleva Cache-Control con la directiva public. Solo si se cumplen las dos abre un hallazgo de severidad media con el nombre de la cookie y el valor de la cabecera. Es deliberadamente estricto: una cookie de preferencias con caché pública, o una cookie de sesión sin public, no se marcan.
Por qué importa
Cache-Control: public es una instrucción explícita a cualquier caché compartida (un CDN, un proxy inverso como Varnish o nginx, el proxy de una empresa u operadora) de que esa respuesta puede guardarse y entregarse a otros usuarios. Si esa misma respuesta incluye un Set-Cookie de sesión, la caché puede guardar la cookie junto con el cuerpo y dársela al siguiente visitante. Ese visitante entra con la sesión del anterior, sin contraseña, y ve sus datos. Es un fallo de los que se descubren cuando un cliente escribe diciendo que "ha entrado en la cuenta de otra persona". Ha pasado en bancos y en tiendas grandes, siempre por la misma combinación: contenido personalizado marcado como cacheable.
Por qué pasa
Normalmente por una regla de caché demasiado amplia: un add_header Cache-Control "public, max-age=3600" en nginx aplicado a todas las rutas, incluidas las que crean sesión; una regla "Cache Everything" en Cloudflare; o un routeRules con cabeceras de caché para '/**' en una app que establece cookies en la página principal. La mayoría de los CDN rechazan guardar respuestas con Set-Cookie por defecto, y nginx también salvo que le digas que ignore esa cabecera, pero un public explícito es tú diciéndoles que lo hagan.
Cómo arreglarlo
- Las respuestas que crean o renuevan sesión llevan
Cache-Control: private, no-store. Es lo que hacen por defectoexpress-session, Laravel, Django y Rails; si vespublic, lo añadió una capa posterior. - Separa las reglas de caché por tipo de ruta.
publicconmax-agelargo para los ficheros estáticos con hash en el nombre (/_nuxt/,/_next/static/,/assets/);privateono-storepara el HTML que depende del usuario. En nginx, usa bloqueslocationdistintos; en Nuxt y Next.js, reglas por ruta en lugar de una global. - No establezcas la cookie de sesión en páginas públicas cacheables. Si la home no necesita sesión, no la cree: crea la cookie al iniciar sesión o al entrar en el panel.
- Si usas un CDN con "Cache Everything" o similar, añade una regla para no cachear respuestas con
Set-Cookieni las rutas autenticadas. Y añadeVary: Cookiecomo red adicional, aunque no sustituye a lo anterior.
Comprueba que está arreglado
curl -sI https://tudominio.com | grep -iE "^(set-cookie|cache-control|vary):"
curl -sI https://tudominio.com/dashboard | grep -iE "^(set-cookie|cache-control):"
Si hay Set-Cookie con nombre de sesión, el Cache-Control de esa respuesta debe ser private, no-store o no-cache, nunca public. Haz la segunda petición con y sin tu cookie de sesión: la respuesta autenticada no debe aparecer nunca sin ella.
Preguntas frecuentes
Mi CDN dice que no cachea respuestas con cookies. ¿Entonces es falso positivo?
Probablemente no se esté produciendo la fuga hoy, pero dependes de un comportamiento por defecto que una regla nueva o un cambio de proveedor pueden desactivar. La cabecera dice que es público; arregla la cabecera y deja de depender de la excepción.
¿Y las cookies de analítica o de consentimiento?
No se marcan: solo cuentan los nombres que indican sesión. Una cookie de consentimiento en una página pública con caché es normal. Cómo deben ir configuradas las cookies de sesión (Secure, HttpOnly, SameSite) lo mira cookies inseguras.
Mi sesión va en un token en localStorage, no en cookie.
Entonces esta comprobación no aplica, pero token en el almacenamiento sí: un token accesible desde JavaScript tiene otros problemas.