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.