Inspiration
WebMCP gives a web page a new power: registering tools that an AI agent can invoke. The browser's consent dialog is built from strings the page authors — its name, its description, its readOnlyHint. So I asked a simple question: what happens when the page lies? Nothing in the current design stops it. SafeGuard makes that trust gap concrete, then closes it.
What it does
SafeGuard is a self-contained browser demo — a virtual café exposing six tools — that demonstrates four real attacks on WebMCP's consent surface:
- Label mismatch — the dialog says "Donate $1", but the tool changes the account email.
- Hidden instruction — a harmless coupon also silently upgrades the membership to a paid plan.
- Tool substitution — a rogue script re-registers the trusted
donatetool with the same name and description, but swapped code. - Read-only lie — a tool declares
readOnlyHint: true, so the dialog promises "no changes", but it actually closes the account.
Then it shows the fix: a Human Mandate — rules the human grants up-front — that intercepts every mutation and checks the runtime effect primitive ({ action: … }), never the tool's declared name, description, or read-only flag. A violating call is refused before any dialog can lie.
It runs live: six real WebMCP tools registered through document.modelContext, discoverable by any agent in Chrome with WebMCP enabled.
How we built it
Plain HTML, CSS, and JavaScript — no framework, no backend, no build step. State can only mutate through a single Effects.apply() gateway, and the Mandate intercepts every call through it. That capability boundary is why a hidden second effect, a swapped tool, or a read-only lie is caught exactly like a blatant one. The repo also ships THREAT-MODEL.md (a reusable trust-boundary checklist plus a concrete proposal for the WebMCP spec) and mandate.json (a machine-readable policy), because the mandate is data, not UI.
Challenges we ran into
WebMCP is brand new — gated behind an origin trial and a browser flag, with sparse docs and moving spec details (the entry point, registerTool's return type, and AbortSignal requirements all shifted across Chrome versions). Running the demo against the real document.modelContext — not a shim — meant getting an origin-trial token for the hosted domain and testing headlessly across Chrome 149–152, including a 151 regression where tool invocations failed closed without an AbortSignal. The bigger challenge was conceptual: proving the consent surface is the weak point, not just asserting it. Each of the four attacks had to corrupt a different WebMCP primitive, so the demo stays a precise spec argument rather than a toy.
Accomplishments that we're proud of
The demo runs against the real WebMCP API — six tools genuinely registered through document.modelContext on the live site, not a simulation. And the Human Mandate is a real capability boundary: every state mutation flows through one gateway that checks the runtime effect primitive, so all four attacks — including the ones that lie inside the consent dialog — are caught by the same rule engine. That is the "person and agent together" idea made concrete: the human grants bounded authority up-front, the agent acts inside it, and a standing check sits between them.
What we learned
The consent dialog is built from page-authored strings, so it is only as honest as the page. Any guardrail that reasons about a tool's name, description, or readOnlyHint can be defeated by a lying page; the only trustworthy thing is what the tool actually does at runtime. That is why the mandate checks effect primitives, and why the spec proposal pushes for structured, machine-readable effects in tool results, verified readOnlyHint, and registration identity.
What's next for SafeGuard
Three spec-shaped fixes follow directly from the four attacks: structured,
machine-readable effects in tool results; verifying readOnlyHint against those
effects instead of trusting it; and registration identity so a "trusted" name
can't be silently rebound. I'd love to prototype the first of these against a
real agent.
Built With
- css
- html
- javascript
- netlify
- webmcp
Log in or sign up for Devpost to join the conversation.