Transformando um hash de CSP num resultado de build, em vez de algo mantido manualmente
Encontrei esse bug por acaso, enquanto verificava uma mudança de analytics completamente sem relação. Uma passagem de browser-verify revelou uma violação de Content-Security-Policy que não tinha nada a ver com o que eu estava testando. Eu podia ter ignorado como ruído. Em vez disso rastreei, e descobri que o botão “Launch Planetary Pathways” na home estava morto em produção havia dois dias, sem nenhum erro visível para quem não estivesse olhando o devtools.
O bug: um hash que já foi verdadeiro
O CSP deste site permite exatamente um script inline por hash sha256, o handler de clique em GamesSection.astro que revela o iframe do jogo. Esse hash foi definido uma vez, lá quando os headers de segurança entraram pela primeira vez, e ninguém mais tinha mexido nele desde então. Depois uma passagem de i18n sem relação nenhuma nesse mesmo componente mudou a saída compilada dele em alguns bytes. O hash parou de bater. astro check continuou verde. npm run build continuou verde. O único lugar onde isso apareceu foi um console de navegador, numa página em que ninguém estava rodando devtools.
Dois dias é bastante tempo para uma funcionalidade de verdade ficar quebrada silenciosamente sem nada pegando isso.
Por que um script recebe hash e outro não?
Este site também tem scripts que não precisam de hash, os scripts de consent-gate do @vdaluz/astro-opt-in-analytics são compilados em arquivos externos, o que evita o problema inteiro. No começo assumi que isso tinha a ver com declarações import: scripts que importam algo ficam empacotados externamente, scripts simples ficam inline. Chute errado. A própria documentação do Astro diz qual é a regra real: scripts pequenos processados ficam inline direto no HTML “para reduzir o número de requisições”. Não existe limite de tamanho documentado, nenhuma flag de config para desligar isso. O script de lançar o jogo é minúsculo, então fica inline. Os scripts de consent-gate acabam puxando código suficiente através dos imports deles para passar de qualquer limite que o Astro use internamente, então são externalizados como efeito colateral de serem maiores, não porque alguém pediu isso.
Essa distinção importa porque significa que você não consegue forçar de forma confiável um script a ser externalizado só reestruturando ele. E se um script continua inline, qualquer hash fixo no código está a uma edição de distância de ficar desatualizado, silenciosamente, para sempre.
A correção: parar de manter o hash na mão de vez
Recalcular o hash uma vez teria corrigido o bug de hoje e preparado exatamente o mesmo bug para a próxima vez que esse componente for tocado. Então, em vez de um patch, escrevi uma pequena integração do Astro que roda depois de cada build: ela varre o HTML gerado de verdade atrás de scripts inline, calcula o hash do que encontrar, e escreve esses hashes no arquivo _headers que vai para o deploy. O hash de CSP não é mais algo que uma pessoa precisa lembrar de atualizar, é um resultado do build, calculado do zero toda vez, batendo com o que o Astro realmente lançou.
Construir isso revelou mais uma coisa que vale a pena saber: minha primeira versão também calculava o hash de um arquivo completamente sem relação, o próprio HTML do build vendorizado do Unity WebGL, que já tem seu próprio override de CSP separado com unsafe-inline. A varredura precisou de uma exclusão explícita para esse caminho, ou toda atualização do engine Unity adicionaria silenciosamente um hash perdido à política do site principal, sem motivo nenhum.
O que eu continuaria fazendo
O servidor de dev quase me mandou pelo caminho errado aqui também, uma checagem de CSP contra npm run dev lança uma violação completamente diferente e sem relação, porque o Astro empacota scripts de um jeito diferente em modo dev. Se eu tivesse confiado nesse primeiro resultado, teria perseguido um fantasma. Fazer o build e pré-visualizar a saída real de produção antes de confiar num resultado “isso parece corrigido” foi o que realmente pegou tanto o bug original quanto o primeiro erro da minha própria integração.
Leitura relacionada
O CSP que só quebrou em produção
Quatro falhas atrás de um único header: middleware morto, regras de _headers que se combinam em vez de sobrescrever, um worker de blob URL sob script-src, e um script que só o edge injeta.
Consertando a lista desatualizada de repositórios fixados do GitHub
Um comentário que dizia 'espelha os repositórios fixados' foi verdade uma vez. Uma lista fixa sem checagem de desvio acaba mentindo sobre o que espelha.
A troca de um token de uma linha que encontrou um botão quebrado e uma lacuna de GDPR
Corrigir um token de analytics de teste virou uma lição sobre como hashes de CSP ficam obsoletos em silêncio, e por que um servidor de dev pode mentir sobre o comportamento em produção.
Você também pode achar útil
NordPass
Gerenciador de senhas da equipe por trás da NordVPN, com um plano gratuito.
Como afiliado da NordPass, ganho com compras qualificadas.
Saiba maisAdGuard para iOS
Bloqueio de anúncios e rastreadores em todo o sistema no iOS, sem necessidade de um servidor DNS separado.
Como afiliado da AdGuard, ganho com compras qualificadas.
Saiba maisProton Pass
Gerenciador de senhas focado em privacidade, da equipe por trás do Proton Mail.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba mais