Quantas instruções escondidas chegam ao seu agente? Na minha demo, 8 de 8
Escondi oito instruções numa página com CSS e HTML. A pessoa não vê nenhuma, o leitor do agente pega as oito. E mesmo lendo o texto renderizado com innerText no Chrome, cinco ainda passam. O que eu faria para defender um agente que lê a web.
Escondi oito instruções numa página de previsão do tempo. Na tela, nenhuma aparece. No texto que um leitor de agente extrai do HTML, aparecem as oito. E quando troquei a extração pelo texto renderizado do navegador, que parecia a correção óbvia, cinco continuaram passando.
Em outubro eu escrevi sobre o texto que só a IA lê, com casos reais. Desta vez quis medir.
O que a demo faz
É o quinto item da série Frontend com IA: 05-hidden-text, em TypeScript estrito. Uma página de exemplo recebe oito técnicas, cada uma com uma instrução falsa e fácil de achar com grep: mesma cor do fundo, fonte de tamanho 0, opacity: 0, display: none, aria-hidden="true", comentário HTML, posicionado fora da tela e content num ::after. Um painel mostra o que a pessoa vê. O outro mostra o que o leitor do agente extrai. O placar conta quantas instruções o agente acha e a pessoa não.
Com as oito ligadas, o placar fica em 8 de 8. Dá para ligar uma por vez pela URL, por exemplo só o ::after.
Veja: não tem modelo nenhum aqui. O leitor do agente é um extrator determinístico, parecido com o que muita ferramenta de “ler página” faz antes de mandar o texto para o LLM. Ele junta o texto sem tags, os comentários e o content do CSS (trecho de src/extractor.ts):
const comments = extractComments(withoutScripts);
const withoutComments = withoutScripts.replace(COMMENT_RE, ' ');
const bodyText = stripTags(withoutComments);
const pseudo = extractStyleContents(`${withoutScripts}\n${css}`);
return [bodyText, ...comments, ...pseudo].join(' '); Então a demo mostra o que chega ao agente. Ela não prova que um LLM obedeceria. Isso depende do modelo, do prompt e das ferramentas que ele tem na mão.
Onde eu errei
Minha primeira reação foi: é só usar o texto renderizado. O innerText do navegador respeita o CSS, e o MDN descreve exatamente isso.
Testei a mesma página da demo no Chrome 156 headless, lendo document.body.innerText. Caíram três técnicas: display: none, comentário e ::after. Passaram cinco: cor igual ao fundo, fonte 0, opacidade 0, aria-hidden e fora da tela. Para o navegador, esse texto está renderizado. Só não dá para ver.
Tem mais um detalhe honesto. A extração “visível” da demo sabe quais classes escondem texto, porque fui eu que montei a página. Uma página de verdade não avisa. É como conferir a mala no raio-x sabendo onde você mesmo guardou o canivete.
Como eu defenderia
Acho que filtrar o HTML ajuda, mas não fecha a porta. Eu montaria a defesa em camadas:
- Conteúdo da web entra como dado. O texto extraído vai num bloco marcado como não confiável, separado das instruções do sistema. Isso reduz o risco, sem zerar. A OWASP coloca prompt injection em primeiro lugar justamente por não ter correção completa.
- Ação com efeito precisa de outra porta. Se o agente pode aprovar um pedido, essa ferramenta não pode depender só do que ele leu. Aprovação humana ou política deny-by-default, como discuti no post sobre hooks de política.
- Logar o que chegou ao modelo. Guardar o texto extraído, e não só a URL, é o que permite explicar depois por que o agente fez o que fez.
- Canário no teste. O truque da demo serve para qualquer pipeline: instruções falsas e únicas, uma por técnica, e um teste que falha se alguma delas aparecer na entrada do modelo. A demo tem um teste por técnica em
src/extractor.test.ts.
O que muda para você
Se o seu agente lê páginas, PDFs ou e-mails, pegue a página da demo, passe pelo seu leitor e conte quantas instruções chegam. Meu palpite é que o número não vai ser zero. O código está no repositório frontend-ia-demos, e as outras demos da série estão em eliseu.dev/projects.
Sou Tech Lead de IA e esse tipo de fronteira entre o que o agente lê e o que ele pode fazer é o que eu gosto de discutir. Se quiser conversar sobre o seu caso, me chama aqui.
Fontes
- Código da demo 05-hidden-text, repositório frontend-ia-demos
- Demo no ar, com
?tecnica=ou?tecnicas=para escolher as técnicas - HTMLElement.innerText, MDN
- LLM01:2025 Prompt Injection, OWASP GenAI Security Project
- Medição do
innerText: a página de exemplo da demo, com as oito técnicas, aberta no Chrome 156 headless em 12/11/2026