Uma landing page criada por IA pode incorporar checkout, vídeo, formulário, autenticação ou demonstração em um iframe. Também pode abrir um pop-up e esperar uma confirmação. Quando os dois documentos pertencem a origens diferentes, a política de mesma origem impede acesso direto ao DOM, mas a API window.postMessage() oferece um canal controlado de comunicação.
O canal só é seguro quando os dois lados sabem exatamente com quem falam e quais mensagens aceitam. Enviar para "*", confiar apenas em um campo type ou inserir event.data no HTML transforma uma integração conveniente em caminho para vazamento, ação indevida ou DOM XSS.
Este guia mostra como inventariar o protocolo, limitar origens, validar remetente e dados e confirmar o comportamento na URL publicada.
Modele postMessage como uma API pública
Qualquer janela que obtenha uma referência para outra pode tentar enviar mensagens a ela. Isso inclui:
- a página pai e seus
iframes; - um pop-up e a janela que o abriu;
- documentos dentro da mesma hierarquia de frames;
- uma janela que navegou para outra origem depois de ser aberta.
O listener não deve concluir que uma mensagem é confiável só porque ela chegou ao evento message. Ele precisa verificar identidade e contrato antes de agir.
Pense em cada mensagem como uma requisição de API:
emissor permitido:janela esperada:tipo da mensagem:campos obrigatórios:campos proibidos:ação disparada:resposta possível:dados sensíveis envolvidos:A documentação de window.postMessage da MDN recomenda origem exata no envio, validação de origin e possivelmente source no recebimento e validação da sintaxe dos dados.
Encontre emissores e listeners
Procure na fonte e nas dependências diretamente integradas:
rg -n \ 'postMessage\(|addEventListener\(["'"']message|onmessage\s*=|MessageChannel|event\.origin|event\.source' \ src publicPara cada ocorrência, registre:
arquivo e linha:envia ou recebe:janela relacionada:origem local:origem remota:targetOrigin:origin validado:source validado:schema da mensagem:efeito produzido:Não pare no seu código. Widgets podem pedir um listener na integração, e bibliotecas podem esconder a chamada. Leia a documentação do fornecedor e inspecione o bundle apenas para confirmar o comportamento relevante, sem editar código gerado.
Se a página não precisa receber mensagens, remova o listener. Segundo a MDN, não registrar um listener é a forma mais segura de evitar problemas de recebimento quando nenhuma comunicação é esperada.
Envie somente para uma origem exata
O segundo argumento de postMessage define qual origem pode receber a mensagem:
const checkoutOrigin = "https://checkout.exemplo";
checkoutFrame.contentWindow?.postMessage( { type: "checkout:set-theme", theme: "light", }, checkoutOrigin,);A origem inclui esquema, host e porta. https://checkout.exemplo e http://checkout.exemplo não são equivalentes. Uma porta não padrão também faz parte da comparação.
Evite:
checkoutFrame.contentWindow?.postMessage(payload, "*");O curinga permite a entrega independentemente da origem atual da janela. Isso importa porque um frame ou pop-up pode navegar entre o momento em que a referência foi obtida e o momento do envio.
Se existem várias origens legítimas por ambiente, selecione uma configuração fechada:
const checkoutOrigins = { development: "http://localhost:4173", production: "https://checkout.exemplo",};Não derive targetOrigin de query string nem aceite a própria URL do frame sem validar. Configuração dinâmica precisa ser comparada com uma allowlist antes de virar destino.
Valide origin e source antes do conteúdo
No recebimento, rejeite cedo qualquer remetente inesperado:
const checkoutOrigin = "https://checkout.exemplo";const checkoutWindow = checkoutFrame.contentWindow;
window.addEventListener("message", (event) => { if (event.origin !== checkoutOrigin) return; if (event.source !== checkoutWindow) return;
// validar event.data antes de usar});Compare a string completa. Condições como estas são frágeis:
event.origin.includes("checkout.exemplo");event.origin.endsWith("exemplo.com");Um host controlado por outra pessoa pode conter o mesmo trecho ou terminar com uma sequência parecida. Construa a allowlist com origens normalizadas e compare por igualdade.
event.origin identifica a origem do emissor no momento do envio. event.source identifica o objeto de janela que enviou. Validar ambos evita aceitar uma mensagem de outra janela que compartilhe uma origem aprovada, mas não participe daquele fluxo.
Quando há múltiplos frames legítimos, associe cada contentWindow ao protocolo que ele pode usar. Não transforme uma allowlist de origens em autorização para qualquer ação.
Valide o formato como dados não confiáveis
Depois da identidade, valide a mensagem. Um type reconhecido não garante que os outros campos sejam seguros:
function isCheckoutCompletedMessage(value) { return ( typeof value === "object" && value !== null && value.type === "checkout:completed" && typeof value.orderId === "string" && value.orderId.length > 0 && value.orderId.length <= 100 );}
window.addEventListener("message", (event) => { if (event.origin !== checkoutOrigin) return; if (event.source !== checkoutFrame.contentWindow) return; if (!isCheckoutCompletedMessage(event.data)) return;
showConfirmation(event.data.orderId);});Defina tipos de mensagem específicos, campos permitidos, tamanho máximo e valores enumerados. Rejeite propriedades desconhecidas quando elas puderem alterar comportamento.
Não execute mensagens como código:
- não use
evalouFunction; - não passe comandos arbitrários para seletores ou roteadores;
- não injete strings em
innerHTML; - não trate uma URL recebida como navegação aprovada;
- não autorize uma operação apenas porque o frontend pediu.
Para mostrar texto, prefira textContent. Para ações sensíveis, envie um identificador mínimo e deixe o backend validar sessão, autorização e estado atual. O guia de prevenção de DOM XSS aprofunda os sinks perigosos.
Mantenha o protocolo pequeno e versionado
Mensagens genéricas como { action: "run", payload: ... } crescem até virar uma API sem fronteiras. Prefira um conjunto fechado:
checkout:readycheckout:resizecheckout:completedcheckout:cancelledPara cada tipo, documente direção, campos e efeito. Se o formato precisar evoluir, inclua uma versão explícita:
{ version: 1, type: "checkout:completed", orderId: "ord_123"}Não envie tokens, cookies, respostas completas de API ou dados pessoais só porque o structured clone suporta objetos complexos. O destinatário deve receber o mínimo necessário para aquele passo.
Considere também repetição e ordem. Um evento pode chegar depois de a pessoa fechar o modal ou trocar de fluxo. A aplicação precisa confirmar que a mensagem ainda corresponde à instância ativa antes de atualizar a interface ou iniciar uma operação.
Diferencie comunicação de autorização
postMessage entrega dados entre documentos; ele não concede permissão de negócio. Mesmo uma mensagem vinda da origem correta pode estar errada, antiga ou ter sido produzida após uma falha no emissor.
Quando a mensagem solicita uma ação autenticada:
- valide origem, janela e schema no frontend;
- associe a mensagem ao fluxo ativo;
- envie ao backend apenas a intenção necessária;
- valide sessão e autorização no servidor;
- trate repetição e idempotência;
- retorne um resultado sem expor dados além do necessário.
Não use uma confirmação recebida do frame como prova única de pagamento, identidade ou assinatura. Confirme estados críticos no serviço responsável.
O artigo sobre proteção de formulários contra CSRF cobre requisições autenticadas forjadas. CSRF e mensagens entre janelas são canais diferentes, mas ambos exigem que o backend não confunda uma solicitação do navegador com autorização suficiente.
Revise iframes e pop-ups em conjunto
O protocolo depende de como a outra janela foi obtida:
Iframe
- confirme o
srce a origem depois de redirects; - mantenha referência ao
contentWindowesperado; - combine o listener com uma política de incorporação adequada;
- use
sandboxeallowapenas com capacidades necessárias; - não aceite mensagem de qualquer frame da página.
Pop-up
- abra apenas após uma ação clara da pessoa;
- valide a URL e use origem exata;
- trate o bloqueio do pop-up e retorno
null; - verifique se a janela foi fechada;
- não preserve
window.openerquando não houver comunicação necessária; - não envie dados depois que o pop-up navegou para uma origem inesperada.
Se o pop-up só precisa abrir um destino e não responder, corte a ligação. O guia de reverse tabnabbing mostra como revisar noopener, links externos e window.open.
Teste cada lado com origens controladas
Monte uma matriz que represente o protocolo real:
| Caso | Resultado esperado |
|---|---|
| origem e janela corretas, schema válido | mensagem processada |
| origem errada | mensagem ignorada |
| origem correta, janela errada | mensagem ignorada |
| schema incompleto ou tipo desconhecido | mensagem ignorada e erro controlado |
| payload grande ou valor fora do limite | mensagem rejeitada |
| mensagem repetida ou fora do fluxo ativo | nenhum efeito duplicado |
| frame ou pop-up navegado para outra origem | envio não revela dados |
Use apenas páginas e contas de teste autorizadas. No DevTools, registre metadados suficientes para diagnosticar sem imprimir tokens ou dados pessoais.
Automatize emissores falsos em origens locais separadas para provar que a allowlist funciona. Uma única execução feliz não demonstra que mensagens hostis são rejeitadas.
A HTML5 Security Cheat Sheet da OWASP recomenda origem exata, validação de event.data e tratamento das mensagens somente como dados.
Publique sem mudar o contrato de confiança
O Site no Ar publica o pacote preparado pelo projeto conforme o guia de Publicação. A hospedagem não descobre automaticamente quais origens deveriam participar do seu protocolo e não substitui "*" por uma allowlist.
Antes de publicar:
- inventarie todos os emissores e listeners;
- remova canais sem uso;
- defina origens por ambiente;
- valide
origin,sourcee schema; - reduza dados e ações disponíveis;
- teste rejeições, redirects e repetição;
- gere o pacote final revisado.
Depois, teste na URL padrão e em cada domínio próprio. A origem da landing muda quando esquema, host ou porta mudam. Se um parceiro envia mensagens de volta para uma lista de origens autorizadas, a URL final precisa constar nessa configuração.
Se o domínio mudar, atualize os dois lados do protocolo, gere uma nova versão e republique o mesmo Site. Evite curingas como atalho para colocar a integração no ar.
Prompt copiável para o agente
Revise o uso de window.postMessage desta landing page antes de publicar.
Pasta publicável: [caminho]URL de teste: [URL]URL padrão: [URL]Domínios próprios: [lista]Iframes, pop-ups e fornecedores: [lista]
- Inventarie todos os emissores e listeners de message.- Documente direção, origem, janela, tipos, campos e efeitos de cada mensagem.- Substitua targetOrigin="*" por origens exatas quando o destino for conhecido.- Valide event.origin por igualdade e event.source pela janela esperada.- Valide schema, tamanho e valores de event.data antes de agir.- Trate mensagens somente como dados; não use eval, Function ou innerHTML.- Não envie tokens, cookies, dados pessoais ou respostas completas sem necessidade.- Não trate uma mensagem do frontend como autorização de negócio.- Teste origem errada, janela errada, schema inválido, repetição e redirect.- Não altere fornecedores ou backends sem aprovação.- Entregue o protocolo encontrado, o diff, os testes e os riscos restantes.Checklist final
- emissores e listeners foram inventariados;
- canais sem uso foram removidos;
- cada envio usa
targetOriginexato quando possível; - cada listener valida
event.originpor igualdade; -
event.sourceé conferido contra a janela esperada; - mensagens seguem um schema pequeno e versionado;
- strings recebidas não chegam a sinks de HTML ou código;
- dados sensíveis não atravessam o canal sem necessidade;
- ações críticas continuam autorizadas no backend;
- erros, repetição, redirects e fechamento foram testados;
- URL padrão e domínios próprios constam no contrato;
- a Version publicada corresponde aos arquivos inspecionados.
postMessage não é inseguro por definição. Ele é uma fronteira explícita entre documentos. Quando destino, remetente, formato e efeito ficam limitados, a integração preserva a separação entre origens em vez de abrir um canal genérico dentro dela.
