O CDN mostrou a conta de outra pessoa: cache de página logada não é público
Marque respostas com sessão como private ou no-store, nunca cacheie Set-Cookie no CDN e separe o miolo público do pedaço personalizado da página.
Terça, 10h15. A loja ligou o cache do CDN na home e o tempo de resposta caiu de 800 ms para 40 ms. Às 10h40 chegou o primeiro chamado: "entrei no site e apareceu 'Olá, Ana' e o carrinho dela". A home de quem estava logado entrou no mesmo cache da home anônima, e o CDN passou a entregar a versão da Ana para todo mundo. A resposta curta: resposta que depende de quem pediu nunca vai para cache compartilhado. Marque essas respostas com Cache-Control: private ou no-store, cacheie no CDN só o que é igual para todos e nunca deixe uma resposta com Set-Cookie ser guardada por um intermediário.
Velocidade e vazamento vieram no mesmo pacote porque o cache não sabia de quem era a página. Ele só sabia a URL, e a URL da home é a mesma para todo mundo.
Por que o CDN entrega a página errada
Um cache compartilhado, seja CDN, proxy reverso ou cache de operadora, guarda a resposta sob uma chave. Na configuração padrão, essa chave é basicamente o método, o host e a URL. O cookie de sessão, que é o que diferencia a Ana do visitante seguinte, normalmente fica de fora.
Quem diz ao cache se ele pode guardar é a sua aplicação, pelo cabeçalho Cache-Control. Se a resposta diz public, max-age=300, o cache entende que pode guardar por cinco minutos e entregar a qualquer um. Muitos vazamentos vêm de um middleware que aplica esse cabeçalho a todas as rotas, ou de uma regra no painel do CDN que força cache por extensão ou caminho, ignorando o que a aplicação mandou.
O Vary: Cookie parece a saída: ele diz que respostas diferentes dependem do cookie. Na prática, CDNs tratam o Vary de formas diferentes, e cada visitante tem um cookie único, então a taxa de acerto do cache vai a zero. Para conteúdo personalizado, a resposta certa é não guardar no intermediário.
Os valores de Cache-Control que importam aqui
public, max-age=60, s-maxage=300: qualquer cache pode guardar. O navegador por 60 segundos, caches compartilhados por 300.private, max-age=0: só o navegador da própria pessoa pode guardar; intermediários não.no-store: ninguém guarda, nem o navegador. Para extrato, dados pessoais e páginas de conta em computador compartilhado.no-cache: ao contrário do que o nome sugere, permite guardar, mas exige revalidar com o servidor antes de usar. Não é o que protege dado privado.
Como separar o público do personalizado
- Liste as rotas que dependem de sessão: conta, carrinho, pedidos, checkout e qualquer página que mostre nome ou dado da pessoa.
- Marque essas respostas como privadas na aplicação, num lugar central que olha se há sessão, e não rota a rota.
- Tire o personalizado das páginas públicas. A home fica igual para todos e o "Olá, Ana" e o contador do carrinho chegam por uma chamada separada, como
/api/eu, comno-store. - Nunca cacheie respostas com
Set-Cookie. Se uma página anônima cria sessão, o cache pode entregar o mesmo cookie de sessão para todos os visitantes. - Configure o CDN para respeitar os cabeçalhos da origem, ou para não cachear quando houver cookie de sessão na requisição.
- Não coloque dado pessoal na URL. Token ou e-mail na query vira chave de cache e aparece em log de CDN.
- Teste com e sem sessão e compare os cabeçalhos.
O middleware em Express
app.use((req, res, next) => {
if (req.session && req.session.usuarioId) {
res.set('Cache-Control', 'private, no-store');
} else {
res.set('Cache-Control', 'public, max-age=60, s-maxage=300');
}
next();
});
app.get('/api/eu', (req, res) => {
res.set('Cache-Control', 'no-store');
if (!req.session || !req.session.usuarioId) return res.json({ logado: false });
res.json({ logado: true, nome: req.session.nome, itensNoCarrinho: req.session.itens || 0 });
});
A regra fica num lugar só, antes das rotas, e o padrão para quem tem sessão é o mais seguro. Rotas anônimas que criam sessão, como o primeiro acesso que gera cookie, também precisam sair do cache público: confira se a sua biblioteca de sessão só cria o cookie quando há algo para guardar.
O mesmo cuidado no Nginx como cache
proxy_cache_path /var/cache/nginx/loja keys_zone=loja:10m max_size=1g inactive=60m;
server {
listen 443 ssl;
server_name loja.exemplo.com.br;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_cache loja;
proxy_cache_bypass $cookie_sessao;
proxy_no_cache $cookie_sessao;
add_header X-Cache-Status $upstream_cache_status;
}
}
Com o cookie sessao presente, o Nginx nem consulta (proxy_cache_bypass) nem grava (proxy_no_cache) a resposta no cache. Por padrão, o Nginx também não guarda respostas com Set-Cookie nem com Cache-Control private ou no-store, a menos que alguém tenha mandado ignorar esses cabeçalhos com proxy_ignore_headers. Se encontrar essa diretiva na configuração, desconfie dela primeiro.
Como conferir
curl -sI https://loja.exemplo.com.br/ | grep -i -E "cache-control|age|set-cookie|x-cache"
curl -sI -H "Cookie: sessao=teste123" https://loja.exemplo.com.br/minha-conta | grep -i -E "cache-control|age|x-cache"
A primeira chamada deve mostrar public e nenhum Set-Cookie. A segunda deve mostrar private ou no-store, e nenhum Age, que indicaria resposta servida do cache. Cada CDN tem seu próprio cabeçalho de diagnóstico, como o CF-Cache-Status do Cloudflare; procure o do seu na documentação dele.
Se o vazamento já aconteceu
Primeiro, pare a fonte: corrija o cabeçalho ou desligue a regra de cache do CDN. Depois, purgue o cache das rotas afetadas, porque as cópias erradas continuam lá até expirar. Se o cache guardou respostas com Set-Cookie, invalide as sessões criadas no período, já que várias pessoas podem ter recebido o mesmo identificador. Por fim, avalie o que foi exposto e para quantas pessoas; dado pessoal exposto pode ter obrigações de comunicação, e o ponto de partida é LGPD num sistema pequeno.
Erros comuns
- Middleware que aplica
publica tudo. Uma rota logada esquecida basta para o vazamento. - Regra no CDN que força cache por caminho. Ela passa por cima do que a aplicação disse e cacheia a página de conta.
- Cachear resposta com
Set-Cookie. Vários visitantes recebem o mesmo cookie de sessão e passam a compartilhar a conta. - Confiar no
Vary: Cookie. Suporte inconsistente entre CDNs e cache que nunca acerta. - Achar que
no-cacheimpede o armazenamento. Ele só exige revalidação; a cópia continua guardada. - Dado pessoal na query string. Vira parte da chave de cache e aparece em logs de terceiros.
Checklist
- Toda resposta com sessão sai com
privateouno-store. - A decisão fica num middleware central, não espalhada por rota.
- Páginas públicas não mostram dado da pessoa; o personalizado vem por chamada separada.
- Nenhuma resposta com
Set-Cookieé guardada pelo CDN. - Não há
proxy_ignore_headersnem regra de CDN que force cache em rota logada. - URLs não carregam token, e-mail ou documento.
- Você testou com
curlas mesmas rotas com e sem cookie de sessão.
Faça agora o teste do curl com cookie numa rota da área logada do seu site: o cabeçalho que voltou diz que a resposta é privada, ou é igual ao da home anônima?
Referência: https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control