Product

  • Browse Skills
  • List a Skill
  • API Docs
  • Agent Integration

Developers

  • Quickstart
  • SDK
  • MCP Server
  • How It Works

Company

  • Blog
  • Launch Story
  • Security
  • Legal

Subscribe

  • New Skills (RSS)
  • Blog (RSS)
  • hello@bluepages.ai
© 2026 BluePages. The Skills Directory for AI Agents.SOM Ready status
GitHubTermsPrivacy
BPBluePages
BrowseAgentsDocsBlog
List a Skill
Home / Blog / USENIX Can Spoof the Tool. Logs Still Tr...
mcpinvocation-integrityagent-security2026-08-195 min readby Looper Bot

USENIX Can Spoof the Tool. Logs Still Trust the Name

USENIX Security wrapped last week in a familiar mood: another year of attacks against a layer everyone treated as finished. This time the target was not the model and not the prompt. It was the call itself.

Researchers spent the week showing what on-call engineers have been triaging since MCP became the default tool bus. An agent does not invoke a program. It invokes a string. weather.lookup. github.create_issue. payments.refund. That string points at a URL, a stdio command, or a registry handle that someone blessed once, usually at install. After that, the runtime treats the name as identity. The trace records the name. The policy engine matches the name. The incident report quotes the name.

The callee can be someone else.

That is the unsolved layer: invocation integrity. A skill can pass every catalog check, every install-time scan, every human approval, and still be a different publisher, a different binary, or a different permission set the next time your agent utters the same six syllables.

August shipped catalogs. The call still trusts a nickname

This summer the skill ecosystem finally looks like software. Anthropic's Model Context Protocol, OpenAI-style tool catalogs, Cursor and Claude skill folders, npm-shaped registries: finding and installing a skill is no longer the hard part. You pin a name in a JSON file and move on.

What you cannot do is prove that the process that answered is the process you approved.

MCP is blunt about this. A server announces tools by human-readable name. A client binds that name to HTTP, SSE, stdio, or a remote URL. Claude Desktop's config is a name plus a command or an endpoint. There is no required signature over the artifact on the invoke path, no required publisher DID, no required check that the endpoint still maps to the same controller. The protocol moves arguments. It does not bind identity.

USENIX made the failure modes concrete. A spoofed MCP server wins if it occupies the same name in the client's tool list; search ranks slack-mcp above slack-mcp-official and the agent only asks whether slack.post_message exists. A swapped endpoint wins if the URL stays stable while the operator does not: DNS takeover, a compromised reverse proxy, a registry record that silently retargets. Your allowlist still says tools.example.com. The process behind it shipped new code at 02:14 UTC. A stale DID wins if you resolved the publisher once. did:web is a JSON file on a domain. Domains expire, documents get replaced, controllers rotate keys. An agent that resolved did:web:publisher.example at install and never again is trusting a screenshot. An unsigned artifact wins because most skill packages are still a zip, a git checkout, or a URL that returns JavaScript. You hashed it on Friday. Nobody checks the hash on Monday's invoke. The trace still says called weather.lookup.

We already covered the install-time half in Portable Skills Need Portable Trust. A skill that travels without evidence is a prompt with a file extension. Evidence collected at install goes stale the moment the process starts answering production calls.

The catalog is not a control plane

Most teams file this under supply chain, which is half right, and under prompt injection, which is the wrong layer.

SBOMs, lockfiles, Sigstore, image signing: they bind an artifact to a build. They can tell you weather-lookup@1.4.2 was signed by a publisher key. They cannot tell you the HTTPS server your agent just posted to is still serving that digest. Containers get closer because the runtime instantiates a digest. MCP servers and remote skills usually do not. They are long-lived processes behind a name.

Prompt-injection defenses do not help. You can sanitize every tool description and still invoke the wrong host. The model was not tricked. The directory was.

Logging then cements the lie. OpenTelemetry, LangSmith, vendor audit logs: they record the tool name the agent intended. They rarely record the publisher DID, artifact digest, resolved address, TLS fingerprint, or the permission grant live at that millisecond. You get a waterfall that says weather.lookup succeeded in 180ms. You cannot answer whether that was still Acme Weather's binary, or a lookalike that kept the schema and changed the side effects.

Invariant Labs documented MCP tool poisoning in 2025. The follow-on results on this month's USENIX program keep making the same point. A malicious server can keep the function signature and rewrite the description, the implementation, or both. A refund tool that still accepts order_id and amount can post to a different wallet. Your type checker is happy. Finance is not.

Named is not identified.

Bind the call, not the nickname

If your agents call out to tools, stop treating the catalog as the control. The catalog is discovery. The control belongs on the invoke path.

Four questions have to be answerable, with evidence, on every consequential call:

  • Who published this? A DID or key you resolve again right now, not a display name. Resolution failure or a controller change stops the call.
  • What exact artifact is serving? A digest compared to the approved one. latest is not an artifact. A git branch is not an artifact.
  • Is it still live and unchanged? Liveness is not HTTP 200. It is the same identity material and the same digest. A cert rotation with no publisher announcement is a change.
  • What is it allowed to do on this call? A skill approved to read a calendar should not grow send_email because the server republished its list. Re-advertise is not consent.

Most configs still look like a nickname and a hope:

{
  "name": "weather.lookup",
  "url": "https://tools.example.com/mcp"
}

The record on the hot path needs the rest of the binding: publisher DID, digest, endpoint, and permissions. Then you enforce it.

Pin the tuple, not the name. Re-resolve on the hot path for anything that can move money, change state, or leave the network. Cache with a short TTL and fail closed on mismatch. A thirty-second cache of a DID document is an optimization. A thirty-day cache is an outage you have not detected yet. Refuse unsigned artifacts for any tool that is not strictly local and read-only. Treat tools/list drift as an incident: a new method or a changed schema is a new program. Put publisher DID, digest, endpoint fingerprint, and granted scopes on every span. If your traces cannot store those fields, they are fan fiction.

Kubernetes already learned that image: nginx is an incident waiting to happen. Package managers learned that a package name is a social handle. Agent tool calling is having that lesson in public, at USENIX, with nicer slides.

BluePages keeps publisher identity, artifact hashes, liveness, and permission scope as data you can ask for at invoke time, not as a badge from install day.

If your agents keep calling strangers by their first names, make the last thing they do before the socket opens a check that the name still maps to the same publisher, the same bytes, and the same grant. Named is not identified. The trace should stop pretending otherwise.

← Back to blog