Cross-site scripting acontece quando dados que deveriam ser exibidos se tornam código no navegador. A entrada pode vir de formulário, URL, banco, API ou dependência comprometida. Output encoding é uma defesa central, mas somente quando corresponde ao contexto. Texto HTML, atributo, URL, CSS e JavaScript seguem regras diferentes.
O navegador interpreta várias linguagens
Uma página contém markup, scripts, estilos e destinos de navegação. Um valor seguro entre tags pode encerrar uma string JavaScript. Um valor preso dentro de href ainda pode usar um esquema perigoso.
Framework auto-escaping protege casos comuns. Raw HTML, innerHTML e event handlers escapam dessa proteção.
Escape no boundary final
Salvar input HTML-encoded mistura apresentação e dado. O mesmo valor pode depois ir para JSON, CSV ou e-mail. Preserve o valor sem apresentação e encode ao renderizar.
Templates devem escapar por default. Raw output precisa de justificativa e tipo confiável.
Evite contextos difíceis
Não coloque dados não confiáveis diretamente em script, style ou atributos de evento. Passe dados por JSON ou data attributes seguros e use APIs estruturadas.
No DOM, prefira textContent a innerHTML para texto. Design seguro remove a oportunidade de virar código.
HTML permitido precisa de sanitizer
Editores ricos podem aceitar tags limitadas. Um sanitizer deve parsear e aplicar allowlist de elementos, atributos e schemes. Precisa entender HTML malformado como o browser.
Regex não é sanitizer. String replacement não cobre nesting e error recovery.
URLs exigem validação além de encoding
Escapar aspas mantém o valor no atributo, mas não torna o destino seguro. Valide scheme, host ou path. Open redirect pode estar perfeitamente encoded.
Policy de navegação e segurança sintática são controles separados.
CSP reduz impacto
Content Security Policy pode bloquear scripts inesperados, usar nonce e gerar relatórios. É defense in depth, não substituição.
Policies permissivas e exceções legadas deixam gaps. Contextual encoding continua obrigatório.
Código cliente pode reintroduzir XSS
O servidor renderiza com segurança, mas JavaScript lê uma query e usa innerHTML. Audite DOM sinks e widgets. Trusted Types pode restringir operações perigosas.
Testes precisam observar o DOM final depois dos scripts.
Stored XSS pode esperar meses
Payload salvo em perfil ou ticket pode executar depois em painel administrativo. Dados armazenados continuam não confiáveis em toda saída.
Interfaces internas merecem a mesma proteção. Seus usuários costumam ter mais privilégios.
Migrações de framework precisam de auditoria
Trocar template engine pode mudar defaults. Inventarie raw helpers, sanitizers e DOM sinks antes. Regression tests com payloads reais protegem a migração.
Um helper que antes recebia texto pode passar a esperar HTML, criando XSS ou double encoding.
Ferramentas de segurança também devem escapar
Logs e dashboards não podem executar o payload investigado. Mostre valores como texto. Relatórios CSP e sanitizer recebem conteúdo hostil por definição.
Documentação interna com exemplos também precisa neutralizar markup.
Incidentes exigem correção da fronteira
Apagar um registro não resolve. Encontre o sink, revise locais semelhantes, invalide sessões expostas e adicione o payload aos testes.
Prevenir XSS é manter uma fronteira clara: dados permanecem dados, código permanece código, e HTML permitido passa por sanitization explícita.
Componentes de terceiros precisam de isolamento
Widgets podem receber dados e usar APIs DOM inseguras fora do controle do template server-side. Revise integrações, aplique CSP e, quando possível, use iframe sandbox para conteúdo menos confiável.
Atualizações de dependência podem mudar sinks e escaping. Security regression tests devem rodar após upgrades relevantes.
Trusted Types pode reforçar aplicações grandes
Trusted Types restringe sinks perigosos para valores criados por policies aprovadas. Ele torna inserções acidentais mais visíveis e concentra sanitization. A adoção exige inventário e migração, mas reduz o uso livre de strings em innerHTML.
Não substitui output encoding. É uma camada que ajuda a preservar a arquitetura ao longo do tempo.
Observabilidade deve ser segura
CSP reports, rejeições do sanitizer e tentativas de raw rendering podem gerar métricas úteis. Os dashboards precisam escapar os próprios payloads, porque estão exibindo dados hostis.
Uma ferramenta de investigação que executa a carga transforma resposta a incidente em nova vulnerabilidade.
Stored XSS exige procurar além da página afetada
Quando um payload foi persistido, ele pode aparecer em painéis administrativos, e-mails HTML, previews, exports e ferramentas internas. Corrigir apenas a tela onde houve o alerta deixa outros sinks ativos. A resposta deve localizar todos os consumidores do campo e entender se dados históricos precisam ser neutralizados ou apenas renderizados corretamente.
Também é necessário avaliar sessões e ações realizadas no período. XSS executado com privilégios administrativos pode ter lido tokens, alterado configurações ou criado credenciais. O incidente é uma falha de fronteira de confiança, não apenas um registro malformado.
Testes de regressão devem usar o parser real
Assertions que procuram <script> na string de resposta não cobrem atributos quebrados, SVG, URLs perigosas nem mutações do DOM. Uma suíte útil combina testes unitários dos helpers com testes em navegador que observam se algum script executa e qual DOM foi produzido.
Payloads de incidentes reais devem virar fixtures permanentes. Eles documentam o contexto vulnerável e protegem futuras migrações de framework, mudanças no sanitizer e refactors de componentes.
CSP deve evoluir com a aplicação
Relatórios em modo de observação ajudam a descobrir scripts inline e origens necessárias antes de bloquear. Depois, nonces ou hashes permitem remover exceções amplas. A policy precisa ser monitorada: adicionar unsafe-inline para resolver uma quebra pode eliminar uma parte importante da proteção.