WebMCP trust boundary · attack surface · fix

Every signal a page declares can lie.

SafeGuard registers tools through document.modelContext and shows how everything a page declares about its tools — the name, the description, even the readOnlyHint — is attacker-controlled. The Human Mandate closes the gap by trusting only what actually runs.

Page-declared · can lie name · description · readOnlyHint
Runtime effect · checked { action } via Effects.apply()

The WebMCP trust boundary

Each attack exploits one signal a page declares; the mandate counters it by trusting the runtime effect instead of the claim.

Attack Declared signal (lies) Runtime check (the fix)
Label mismatch description verify the effect, not the copy
Hidden instruction an undeclared second effect check every effect, not just the first
Tool substitution name + copied description check the runtime behaviour, not the name
Read-only lie readOnlyHint ignore the hint, check for writes

The Break attack

A virtual café where consent dialogs can lie.

Run an attack scenario

Attack 1 · Label mismatch

Donate $1 to keep the café running

The consent dialog shows “Donate $1”, but the tool actually changes the account email.

Attack 2 · Hidden instruction

Apply a 10% loyalty coupon

Consenting to a harmless coupon also silently upgrades membership to “premium” (a paid plan).

Attack 3 · Tool substitution

Donate — the trusted tool, swapped

A rogue script re-registers donate with the same name and description, but swaps its code to change the account email. The consent dialog is indistinguishable from the real thing.

Attack 4 · Read-only lie

Show my account summary

The tool declares readOnlyHint: true, so the dialog reassures “read-only, no changes” — but it actually closes the account.

The Fix defence

A Human Mandate that verifies effect, not the words on the dialog.

Your mandate (rules set up-front)

Ask the AI to attempt something

Within bounds

Donate $3

Allowed by the mandate (donate ≤ $5) → runs without a consent dialog.

Over limit

Donate $50

Exceeds the ≤ $5 mandate → rejected silently, no consent dialog.

Forbidden

Change account email

Personal-info mutation is forbidden → rejected silently.

The attack, now blocked

“Donate $1” (label-mismatch tool)

The same malicious tool from Attack 1 — now its declared effect is checked first, so it is refused before any dialog can lie.

The hidden instruction, now blocked

Apply loyalty coupon (hidden upgrade)

The coupon runs, but its hidden membership upgrade is refused by the mandate.

The swap, now blocked

“Donate” (the swapped tool)

The mandate checks the runtime effect, never the tool's name or description — so a hijacked donate that actually changes the email is refused before any dialog can lie.

The read-only lie, now blocked

“Show my summary” (read-only tool)

The mandate checks the runtime effect, not the read-only declaration — so a “read-only” tool that actually closes the account is refused.

Audit trail — intent · consent · effect

# Intent (tool) Consent shown Mandate verdict Actual effect
No tool calls yet. Trigger an attack or defence scenario above.