Skip to content

mcp_schema: an external $ref in inputSchema hashes as a pointer, so its target is outside both digests #143

Description

@blitzcrieg1

What happens

fingerprint_tools hashes the tool object as given. A JSON Schema $ref is a string, so the digest commits to the pointer, not to what it resolves to.

Verified on master:

$ref pointer hashes identically regardless of what it resolves to: True
ref = [{"name": "send_email", "description": "Send an email.",
        "inputSchema": {"$ref": "https://attacker.example/schema.json"}}]

The server can change what that URL serves, every time, and the fingerprint never moves.

Why this is different from #142

_meta is an exemption we chose and can withdraw. This one is structural: the digest is sound over the bytes it was given, and the bytes are a pointer. Fingerprinting is being asked to commit to a document it never saw.

It is also not a severity-tuning question, so it does not belong inside the #103 bucket split. A dual-digest design would place $ref in the structural bucket, quietly making the loud half blind to it.

Options

  1. Resolve refs before fingerprinting. Most faithful, and turns the recorder into something that fetches URLs an untrusted server names. That is the same objection that stops us polling MCP servers on a schedule, so it is probably out.
  2. Refuse to fingerprint a schema carrying unresolved external refs, and emit unavailable with a distinct reason, the way a failed listing does since 0.7.0. Honest, and says "we cannot commit to this" rather than committing to nothing.
  3. Fingerprint what the client resolved, if the client resolved it, since the proxy sits in the path. Best fidelity where available, silent where not.
  4. Accept and document. Weakest, but at least the limitation would be stated rather than implied, like the tripwire caveat in the README.

Option 2 is my current preference: it converts a silent hole into a visible gap, which is the pattern already used for failed listings and for the four-state coverage attestation.

Local #/$defs/... refs are a separate and easier case, since the target is inside the same document and is already hashed.

Not frozen

core/diagnostics/mcp_schema.py is not part of the ruleset fingerprint.

Source

Reported by @Santoshkumarpuppala in #103.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions