Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -2149,6 +2149,66 @@ page, protecting against malicious scripts or dependencies from using WebMCP API

**How:** This restriction would not fully solve prompt injection attacks but helps shrink the possible universe of attacks, preventing longer prompts that leverage e.g. repetition and sockpuppetting [[SOCKPUPPETTING]] to convince agents of malicious tasks. The specification already implements a nominal size restriction of 128 characters for the tool {{ModelContextTool/name}} (see [[#supporting-concepts]]), but further work is needed to evaluate the right size limits for titles, names, and other inputs. See [Issue #73](https://github.com/webmachinelearning/webmcp/issues/73).

<h4 id="mitigation-invisible-characters">Handling invisible characters in tool metadata</h4>

**What:** Rejecting tool registrations with invisible characters in metadata, rather than silently
removing those characters.

**Threats addressed:** [[#metadata-description-attacks]]

**How:** A tool's {{ModelContextTool/title}}, {{ModelContextTool/description}}, and the strings in
its {{ModelContextTool/inputSchema}} and {{ModelContextTool/outputSchema}} can contain characters
that take no space on screen: Unicode tag characters (U+E0000 to U+E007F), which can encode ASCII
text, zero-width characters, and bidirectional formatting controls that change the displayed order of
text. An instruction hidden this way is no more persuasive to a model than a visible one, but it is
missed by anything that relies on the displayed text, such as code review, a tool list or
confirmation prompt shown to the user, or a guardrail that matches keywords or uses a small model.
This matters most when a script that the site author does not control registers tools on the page.
Tool names are limited to ASCII (see [[#supporting-concepts]]) and are not affected. Length limits
([[#mitigation-restrict-input-lengths]]) do not help, because many of these characters fit within any
practical limit.

Rejecting a registration lets the developer fix the text rather than having the host silently change
it. Stripping descriptive text changes what the developer wrote; stripping property names or values
such as `enum`, `const`, and `default` also changes what the tool accepts or receives. Implementers
considering registration-time validation are encouraged to:
<ul>
<li>Define the rejected character set using the Unicode
[Default_Ignorable_Code_Point](https://www.unicode.org/reports/tr44/#Default_Ignorable_Code_Point)
property as a baseline, rather than a hand-written list. This includes tag characters, zero-width
characters, bidirectional controls, and Hangul fillers. Use the same Unicode version and explicitly
defined exceptions across implementations, so the same metadata is not accepted by one browser
and rejected by another.</li>
<li>Inspect [=code points=], not individual UTF-16 code units. Tag characters are represented by
[=surrogate=] pairs, so a per-code-unit range check misses them. Reject lone surrogates in
{{ModelContextTool/description}} and parsed schema strings as well, rather than replacing or
removing them. The {{ModelContextTool/title}} is a {{USVString}}, whose conversion already
replaces lone surrogates.</li>
<li>Check {{ModelContextTool/title}}, {{ModelContextTool/description}}, and both schemas. After
[=serializing a tool schema=], inspect the parsed JSON recursively, including every object member
name and string value, not only schema descriptions. This covers property names and strings nested
in `enum`, `const`, and `default`, and resolves `\uXXXX` escapes before checking. Checking the
serialized representation also covers changes made by a schema's `toJSON()` method, rather than
inspecting only the original JavaScript object. Reject the registration if any checked string
contains a disallowed character; do not rewrite the schema.</li>
<li>Account for legitimate uses of these characters. A blanket rejection also rejects emoji with
U+FE0F VARIATION SELECTOR-16, emoji sequences using U+200D ZERO WIDTH JOINER, the England,
Scotland, and Wales flag tag sequences, and spellings that need U+200C ZERO WIDTH NON-JOINER in
Persian and Indic languages. If exceptions are chosen, define them in terms of well-formed
sequences and their context, not unrestricted permission for those characters: standalone or
repeated invisible characters can still carry hidden data.</li>
<li>Provide a diagnostic identifying the offending code point (for example, `U+E0061`) or lone
surrogate, and its location, such as the title, description, or a path within a schema. Render the
offending character as a visible escape, rather than embedding it invisibly in the message, so
the developer can locate and fix it.</li>
<li>Show the user the same accepted text that is given to the model when asking for approval.
Do not sanitize only the approval UI while passing unmodified hidden text to the model.</li>
</ul>

This guidance does not add a rejection step to {{ModelContext/registerTool()}}. A normative
registration check would need an interoperable definition of the Unicode version, any exceptions,
the schema checks, and the rejection behavior.

<h4 id="mitigation-shared-attack-evals">Supporting interoperable probabilistic defense structures through shared attack eval datasets</h4>

**What:** Shared evals for prompt injection attacks against WebMCP
Expand Down
Loading