E10 · Seven cases
5 minRun the exposure block.
Done when: 7/7 casos conferem com a spec and saída: 0.
Lesson 4 · 18 min
By default, only the page itself and same-origin pages can see the tool. An iframe from another origin shows up to the top page only with three consents, and a header turns everything off. In this lesson you measure the seven cases in Chromium and put Permissions-Policy: tools=() on the pages that do not need WebMCP.
Start the lesson
Goal: measure who sees each tool (same origin, iframe, exposedTo, fromOrigins) and turn WebMCP off with Permissions-Policy where it should not exist.
Click each one. Top = the main page; iframe = a page from another origin inside it.
Measured in Chromium 156.0.8078.4 with the WebMCP test flag (experimental as of October 19, 2026). The spec's origin boundaries section (§6.3.4) is still just a to-do note.
The top page runs on 127.0.0.1 on one port, and the iframe runs on 127.0.0.1 on another port. Same machine, different port: different origin.
cd ~/projetos/formacao-webmcp-seguranca-v72/kit node scripts/exposicao.mjs; echo "saída: $?"
<!doctype html>
<!-- filho.html — the page inside the iframe (it can be from another origin). Tries to register a tool.
Parameter (lab only): ?exposto=<origin> becomes registerTool(..., { exposedTo: [<origin>] }) -->
<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>== 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
Only in case D does the top page see the iframe's tool, and the result carries its origin (ferramenta_iframe@http://127.0.0.1:…). The agent can use that field to avoid mixing up whose tool it is.
If the tool belongs only to your page, don't use exposedTo. Same origin already sees it, and no other origin needs to.
Chat widget, ad, video: an iframe from another origin without allow="tools". That way it can't register any tool in your document.
Login, payment, privacy: send Permissions-Policy: tools=(). The spec (§6.4.1) says this protects even against injected script or a compromised dependency, because it applies before any script runs.
Seeing the tool doesn't mean being able to do everything. The backend still checks who the person is and what they can do, just like with the button.
The kit's server sends tools=() on the privacy page and nothing on the main page. Alongside it, the same header on three common servers.
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: $?"
location = /privacidade.html {
add_header Permissions-Policy "tools=()" always;
}// before express.static
app.use('/privacidade.html', (req, res, next) => {
res.set('Permissions-Policy', 'tools=()');
next();
});/privacidade.html Permissions-Policy: tools=()
página em http://127.0.0.1:8742/ Permissions-Policy: tools=() 0 saída: 0
The privacy page sends tools=(); the main page sends 0 Permissions-Policy headers (it needs the API).
Measure and turn it off.
Run the exposure block.
Done when: 7/7 casos conferem com a spec and saída: 0.
Run the header block.
Done when: Permissions-Policy: tools=() on the privacy page and 0 on the main page.
Pick the pages on your site that don't register a tool (login, payment, privacy), set the header on your server, and check each one with curl -sI [page address] | grep -i permissions-policy.
Done when: Each page on the list responds with permissions-policy: tools=().
Question 1 of 4
An iframe from another origin registered a tool with exposedTo pointing at the top page, but the top page can't see it. What's missing?skill
Question 2 of 4
Which page should send Permissions-Policy: tools=()?
Question 3 of 4
What happens with exposedTo: ['http://exemplo.com']?
Question 4 of 4
The tool shows up for the top page. Does that authorize any action it takes?
Choose an answer. The explanation appears after you try.