Pré-visualizando sites externos com segurança — implementação de proxy + sanitização + iframe sandbox
- engineering
- security
- nextjs
- iframe
Resumo deste artigo
- Carregar um site externo em um iframe da própria origem traz o risco de o JS externo acessar a sessão do serviço
- A defesa é em camadas: "proxy com proteção contra SSRF + remoção de JS externo + CSP restritivo + iframe sandbox"
- Um sandbox forte demais pode até bloquear os próprios scripts internos. Esquecer de incluir
allow-scriptsfoi uma armadilha que fez a funcionalidade parar de funcionar
O teste A/B do HeatMapX conta com uma funcionalidade que permite "pré-visualizar a aparência após a alteração (Variant B), já aplicada à página de destino". Tecnicamente, isso significa "exibir o site externo do usuário, com segurança, dentro de um iframe do próprio domínio do serviço" — um processo surpreendentemente delicado. Neste artigo, compartilhamos o design da solução e as armadilhas que encontramos na prática.
Por que uma implementação ingênua é perigosa
Se buscarmos o HTML do site externo e o servirmos diretamente pela própria origem (por exemplo, heatmapx.com), qualquer <script> ou onclick= presente nesse HTML passa a ser executado com os privilégios da própria origem. Isso abre a possibilidade de leitura de cookies e sessões de login. É algo que precisamos evitar a todo custo.
Estrutura de defesa em camadas
Para isso, empilhamos as seguintes camadas.
1. Proxy com proteção contra SSRF
Validamos a URL de destino e rejeitamos qualquer esquema que não seja http/https. Além disso, bloqueamos localhost, IPs privados e endpoints de metadados de nuvem (como 169.254.169.254). Os redirecionamentos não são seguidos automaticamente: usamos redirect: 'manual' para revalidar cada salto individualmente, evitando que o fluxo seja direcionado para a rede interna.
2. Remoção do JS externo (sanitização)
Removemos as tags <script>, os manipuladores inline on*= e o esquema javascript: do HTML obtido. Não há necessidade de executar o JS do site do cliente na pré-visualização (é uma limitação conhecida que SPAs não sejam totalmente reproduzidas).
3. CSP restritivo e base href
Inserimos um <base href> logo após o <head> para resolver caminhos relativos de imagens e CSS, e aplicamos à resposta um CSP restritivo que impede a execução de JS externo. Imagens e CSS são permitidos, pois são necessários para a exibição.
4. iframe sandbox
Por fim, isolamos o lado da exibição adicionando o atributo sandbox ao iframe.
A armadilha: o sandbox estava restritivo demais
Na pré-visualização, injetamos no HTML obtido um pequeno script interno que "aplica a alteração (changeSet)" e o executamos dentro do iframe para modificar a aparência. Porém, o sandbox do iframe de pré-visualização estava configurado apenas com allow-same-origin, sem o allow-scripts.
Como resultado, nosso próprio script também deixava de ser executado, causando um bug em que a página original era exibida sem a alteração ser aplicada. O iframe do lado do editor de edição, configurado corretamente com allow-scripts allow-same-origin, funcionava normalmente — a causa era essa divergência de configuração entre os dois.
A correção foi simples: bastou alinhar o lado da pré-visualização também para allow-scripts allow-same-origin. Junto com isso, adicionamos um teste de regressão que valida o atributo sandbox do iframe.
sandbox="allow-same-origin" → sandbox="allow-scripts allow-same-origin"
Lições aprendidas
- Pense na incorporação de conteúdo externo como uma "defesa em camadas". Não confie em uma única medida.
- O
sandboxnão é seguro só por estar presente — o essencial é conceder exatamente os privilégios necessários, nem mais, nem menos. Se for restritivo demais, pode até travar a própria funcionalidade. - Sempre que houver "dois fluxos que fazem o mesmo processo" (como o editor e a pré-visualização), verifique se os atributos de segurança estão alinhados entre eles. Corrigir apenas um lado pode quebrar o outro.
Conclusão
É possível implementar uma pré-visualização segura de sites externos combinando proteção contra SSRF, remoção de JS externo, CSP restritivo e iframe sandbox em camadas de defesa. E os privilégios do sandbox trazem problemas tanto quando são insuficientes quanto quando são excessivos. O esquecimento do allow-scripts neste caso foi um exemplo clássico disso.