Segurança e confiança entre site e agente|INEMA.CLUB-PRO

Aula 4 · 18 min

Quem vê a ferramenta: mesma origem, exposedTo, iframes e Permissions-Policy

Por padrão, só a própria página e as de mesma origem enxergam a ferramenta. Um iframe de outra origem só aparece para o topo com três consentimentos, e um cabeçalho desliga tudo. Nesta aula você mede os sete casos no Chromium e põe Permissions-Policy: tools=() nas páginas que não precisam de WebMCP.

Começar a aula
Duas janelas de navegador sobrepostas em monitor escuro, uma dentro da outra, com um conector entre elas.

Nesta aula você vai

  1. 1Origem = esquema + host + porta, e o padrão de exposição da spec
  2. 2Os três consentimentos do iframe: allow="tools", exposedTo e fromOrigins
  3. 3Permissions-Policy: tools=() desliga a API antes de qualquer script
  4. 4Sete casos medidos no Chromium 156 contra a spec de 16/10/2026

Objetivo: medir quem enxerga cada ferramenta (mesma origem, iframe, exposedTo, fromOrigins) e desligar o WebMCP com Permissions-Policy onde ele não deve existir.

01Os quatro controles de exposição

Clique em cada um. Topo = a página principal; iframe = página de outra origem dentro dela.

Clique em cada bloco do diagrama para ver o que ele faz.

Medido no Chromium 156.0.8078.4 com a flag de teste do WebMCP (experimental em 19/10/2026). A seção de fronteiras de origem da spec (§6.3.4) ainda é só uma nota de pendência.

02Sete casos, duas origens locais

O topo roda em 127.0.0.1 numa porta e o iframe em 127.0.0.1 noutra porta. Mesma máquina, porta diferente: origem diferente.

Terminal · exposição
cd ~/projetos/formacao-webmcp-seguranca-v72/kit
node scripts/exposicao.mjs; echo "saída: $?"
Arquivo · kit/exposicao/filho.html
<!doctype html>
<!-- filho.html — a página dentro do iframe (pode ser de outra origem). Tenta registrar uma ferramenta.
     Parâmetro (só para o laboratório): ?exposto=<origem>  vira  registerTool(..., { exposedTo: [<origem>] }) -->
<meta charset="utf-8">
<title>iframe</title>
<script>
  (async () => {
    const q = new URLSearchParams(location.search);
    const mc = document.modelContext;
    let estado = 'sem WebMCP';
    if (mc) {
      try {
        const opcoes = q.get('exposto') ? { exposedTo: [q.get('exposto')] } : {};
        await mc.registerTool({ name: 'ferramenta_iframe', description: 'Ferramenta do iframe.',
          inputSchema: { type: 'object', properties: {} }, execute: async () => ({ ok: true }) }, opcoes);
        estado = 'registrou';
      } catch (e) { estado = 'recusou: ' + e.name; }
    }
    parent.postMessage({ iframe: estado }, '*');
  })();
</script>
Exemplo do repositório
== Chromium 156.0.8078.4 (Playwright) · modo: nativo
confere 0 · iframe da MESMA origem, sem nada → vê: mesma origem é o padrão
confere A · iframe de outra origem, sem allow="tools" → iframe recusado (NotAllowedError)
confere B · allow="tools", sem exposedTo → topo não vê
confere C · allow + exposedTo:[topo], topo sem fromOrigins → topo não vê
confere D · allow + exposedTo:[topo] + fromOrigins:[iframe] → topo VÊ a ferramenta do iframe
confere E · topo com Permissions-Policy: tools=() → topo e iframe recusados
confere F · exposedTo com origem http:// pública → iframe recusado (SecurityError)
7/7 casos conferem com a spec
saída: 0

Só no caso D o topo vê a ferramenta do iframe, e o resultado traz a origem dela (ferramenta_iframe@http://127.0.0.1:…). O agente pode usar esse campo para não confundir de quem é a ferramenta.

03Regras para o seu site

1. Padrão basta

Se a ferramenta é só da sua página, não use exposedTo. Mesma origem já vê, e nenhuma outra precisa.

04O cabeçalho na prática

O servidor do kit manda tools=() na página de privacidade e nada na página principal. Ao lado, o mesmo cabeçalho em três servidores comuns.

Terminal · cabeçalho
cd ~/projetos/formacao-webmcp-seguranca-v72/kit
node scripts/servir.mjs & P=$!
until curl -s -o /dev/null http://127.0.0.1:8742/; do sleep 0.3; done
curl -sI http://127.0.0.1:8742/privacidade.html | grep -i '^permissions-policy'
curl -sI http://127.0.0.1:8742/ | grep -ci '^permissions-policy'
kill $P; echo "saída: $?"
Modelo · nginx
location = /privacidade.html {
    add_header Permissions-Policy "tools=()" always;
}
Modelo · Express
// antes do express.static
app.use('/privacidade.html', (req, res, next) => {
  res.set('Permissions-Policy', 'tools=()');
  next();
});
Modelo · _headers (Netlify, Cloudflare Pages)
/privacidade.html
  Permissions-Policy: tools=()
Exemplo do repositório
página em http://127.0.0.1:8742/
Permissions-Policy: tools=()
0
saída: 0

A privacidade manda tools=(); a principal manda 0 cabeçalhos Permissions-Policy (precisa da API).

05Prática

Medir e desligar.

E10 · Sete casos

5 min

Rode o bloco exposição.

Pronto quando: 7/7 casos conferem com a spec e saída: 0.

E11 · Cabeçalho do kit

3 min

Rode o bloco cabeçalho.

Pronto quando: Permissions-Policy: tools=() na privacidade e 0 na principal.

E12 · Seu site

20 min

Escolha as páginas do seu site que não registram ferramenta (login, pagamento, privacidade), configure o cabeçalho no seu servidor e confira cada uma com curl -sI [endereço da página] | grep -i permissions-policy.

Pronto quando: Cada página da lista responde com permissions-policy: tools=().

06Teste da aula

Pergunta 1 de 4

Um iframe de outra origem registrou com exposedTo apontando o topo, mas o topo não o vê. O que falta?competência

07Em três frases

PadrãoMesma origem vê; outra origem não vê nada.
IframeSó com allow="tools", exposedTo e fromOrigins juntos.
Desligartools=() nas páginas que não precisam da API.

Para ir além

Aula anteriorPróxima aula