In which we discover that the secret menu is neither secret nor a menu.

When trying to review a new MCP service for security, I’ve heard the argument, “This is not net-new, it’s just the same as our existing APIs,” and therefore it can get rubber-stamped, riding the coattails of APIs gone before.

This is true on the surface: MCP is another way to express an API. Its mainstream support is now streamable HTTP instead of SSE, and after that, there’s just not much special about it. It’s a self-describing API with tools and resources as its basic entry points. It leans on the same authorization primitives that other HTTP services use.

So what’s different?

The audience, the universality, and the new implied contract.

MCP is Just an API

In the earlier days of the internet, it took a long time for many flowers to bloom, many weeds to be cut down (looking at you, CORBA, COM, and SGML), and finally something more like what is ubiquitous today with approximate RESTfulness and JSON. There are other standards out there, like gRPC, which is actually really good and, not unlike what I have done here, recklessly mixes transports, protocols, and wire formats, but in the end it’s JSON, HTTP, and WebSocket that have really won the day.

MCP is not a really special new entry in most ways. It’s basically just another JSON-over-transport thing (especially as SSE goes out of vogue), and a company implements it in much the same way they would an API: by asking junior programmers to turn service handlers into backend calls and watching them forget to add authorization checks. Pretty familiar stuff, except now agents do the forgetting.

And Yet… About Mashups

I want to get more into the agents thing, but first let’s talk about mashups. In the early 2000s, mashups were exciting. Google Maps made an API available and suddenly everyone had a map on their web page. There were some neat things happening at the time. Train schedules turned into real-time visual experiences, local information could show up as pins on the map, bike trails that Google did not know about suddenly appeared on a Google Map because someone, somewhere had data, and Google provided a nice platform to show it.

These mashups were created by joining together information and experiences that involved multiple different APIs. A train schedule coming from a city’s API fed data to an application, and that application fed the data into a map API served by Google and combined their results. That’s the whole idea in a nutshell. At its most basic, a mashup is an app sitting at the center of a bunch of distinct APIs, not unlike an agent sitting in the middle of distinct tools. We’ll come back to that.

The Mashup: One App, Five Bespoke Protocols

A defining characteristic of the mashup world was these bespoke protocols that required an annoying search through usually dead documentation to figure out how to invoke them. As the internet at large became more aware of authorization needs, I personally found myself spending the most time just figuring out how to get the darn things to accept me as worthy to enter. OAuth2 and SAML helped a lot with that, though, as they became de facto standards over time. De facto standards, as it happens, make everything easier. Even then, outside of a few special cases, there was no de facto anything. JSON Schema (Open API) was the biggest innovation applied here, but it was easy to ignore unless your company really cared about openness. Most are incentivized not to care about that.

Then came AI. It all happened so fast that any new player on the market would get immediate uptake and become a de facto standard. There was a lot of hunger for a way to make AI take actions. Anthropic was the new player, MCP the standard.

A Universal Action Protocol

Protocols can be universally applicable without being universally applied. That’s the mashup situation in a nutshell, and it actually has some nice, accidental properties:

  • Mashups have to be carefully coded with knowledge of what they’re doing.
  • When authorizing parts of a mashup (e.g., a mobile app that needs DropBox access), the user knows why they’re granting it.
  • Mashups don’t need every privilege.

All of these immediately go out the window with agents that use MCP, not because it’s a bad protocol, but because its use cases are fundamentally open. Why would you want to authorize an MCP service to an agent? Because you want that agent to do things. What things? All the things.

Agent All The Things

“All the things” means mashups between services: check tickets, write docs, fix code, push to repositories, build containers, watch your budget, do web research. All of these are desirable, maybe not all at once, but certainly from the same interface. That has a few important consequences:

  • Your agent harness has an unprecedented combination of credentials.
  • Your credentials are powerful - they can read, write, change, and basically do anything by default because you want them to.
  • Your credentials are used by an overly trusting tween with a massive memory and really sharp tools.

MCP’s universality is both its blessing and maybe your curse. Any agent made by any company can use any MCP service that you can authorize, and everyone has an MCP service. You can do anything, so you install everything, and it’s easy because the app is your agent. Infinite mashups. Unfortunately, since you have a mashup machine with a perpetual hangover, any content anywhere it looks could potentially confuse it, so a spam message could empty your bank account when you ask your agent for help organizing your email.

Does this usually happen? No, thankfully, it’s not as common as spam itself… yet. Spam wasn’t all that common at first, either. Once it took hold, though, it was everywhere, and that happened partly because email was truly universal. It still is. Now we have that universality with AI and MCP. Both the app and the protocol are universal. This is new.

And with that universality, and the incredibly general capabilities of AI, we are all regularly interacting with infinite mashup machines. Nobody has to pore over documentation anymore to figure out how to use an API that ultimately doesn’t do what it claims anymore, just in order to give you a slightly more functional app. Instead, everyone just exposes MCP on their API server, you point your agent to it, and it can do just about anything with each of those, or all of them together. The sky(net) is the limit!

A Fundamentally New Contract

Universality is its own thing, but there is more. With the previous 15-standards situation, contracts generally had to be explicit, enumerable, and required a lot of client one-offs.

XKCD 927: Standards

Fortunately, the charging one has been solved now that we’ve all standardized on mini-USB. Or is it micro-USB? Shit. XKCD #927: Standards

It was very much a problem when someone exposed even unsanitized form fields, so people (kind of) learned not to do that. Exposing a protocol? Make sure it’s got a limited blast radius, is generally only capable of exactly what you need, and is well sanitized. You can’t, after all, trust any inputs, lest little Bobby Tables have a field day SQL-injecting your school portal.

XKCD 327: Exploits of a Mom

Her daughter is named Help I’m trapped in a driver’s license factory. XKCD #327: Exploits of a Mom

The good cases, where people were not using GraphQL (a whole topic unto itself), all had something in common: explicit, bounded contracts. You went to talk to an API, and it could do what it could do. That was it. Data and actions were well separated. Nouns and verbs. RESTful ideas. Give the API a thing that you choose, and it could do a thing that its creator chose. You were ordering in a restaurant and there was no secret menu.

You may have seen stories along the lines of call tree experiences turning into “how do I write this Python code” sessions. The stories focus a lot on the jailbreaking aspect of those things: the speech recognition engine is now the everything engine, and gosh darn it, the company just didn’t have tight enough system prompts to keep people from learning how to bake rice pudding while ordering their Big Mac.

Those are amusing and usually harmless tales, largely because these systems don’t really have that much access (we hope!). But there is a problem, and it isn’t the companies or the system prompts, it’s far more fundamental than that. The problem is the contract.

Call trees are everyone’s worst nightmare, but they have a fundamental security property: they control the choices. This is also why we all want to just talk to a human: humans are more flexible, understand us better, and if we’re lucky, have a few more buttons on their web interface that will help us get what we need. That’s the traditional API approach.

When a traditional API is exposed, the service owner decides what it is able to do, exposes only that, and you, dear client, are just going to have to take what you can get. The contract is defined by the server, the client adheres to it or doesn’t get what they want. There has been a trend away from this in the last decade or more, especially when GraphQL became popularized by a company that has famously sloppy security practices. In GraphQL, the client has a lot of leeway in what they ask for and the server is often coded by someone who wants this to be easy. We therefore often see kitchen-sink GraphQL endpoints that are a light veneer of JSON over SQL, but that’s at least obviously not a good idea, and folks tend to eventually find ways to fix it that start to look more like traditional APIs (e.g., fixed queries accessed by ID). Good. Crisis averted.

Now imagine that everyone is exposing everything through GraphQL, but it doesn’t take queries, it takes raw text and tries its best to do whatever you ask.

That’s the new AI contract.

AI can be easily confused. Humans can, too, but AI is (for now) quite a bit dumber about social norms, can’t be motivated by the threat of punishment or shame, and has way more access in general than any given person. When we expose a “prompt surface” to a user, we expose everything the supporting agent has privileges to do, even if we expect and instruct it to be bounded. The only way around this is structural: only authorize your embedded agent to do limited work; take back control of actions on the service side. When done right, users can ask but the agent can’t comply. But companies are in a Red Queen’s Race, running to stand still, and if someone else exposes neat new functionality through their in-built chat, everyone else has to follow or fall behind and maybe lose customers. That’s the market, and it pushes us toward this fundamentally new open contract: accept everything, try to do everything.

That’s not a contract. It’s a riddle door, and industry is wrestling with questions of how to balance intelligent, flexible access to the jewels without losing them to the next malicious prompt engineer.

Speak, friend, and enter

It’s really interesting to watch, and frankly, it’s a very exciting challenge to try to navigate this new world if you’re a security researcher. We haven’t seen anything like this before, where the game is a far larger and higher-stakes version of “balance convenience and security”, without defaulting to just “share power and play nice”.

Good Luck, Dear Security Fundamentals

The combination of protocol universality and a market push toward ever more open contracts in products and APIs makes this a fascinating time to live. There’s a lot that is scaring folks, and a lot that is exciting folks, and a lot that is causing massive amounts of money to be spent. There’s just a lot in general, really, and it’s hard to keep up. I think the important thing is to step back and remember that security fundamentals haven’t really changed, they’ve just been exposed to a whole new situation where the easy answers don’t apply.

Security is still about the basics, like data/instruction separation, identity, authorization, credential hygiene, trust boundaries, observability, and sensible controls. None of that has changed, it’s just gotten interesting. It helps to rise above the hype and the fear and the new baseline anxiety by mapping all of this onto those basic principles.

Here are a couple of examples that map nicely:

“Why is the open contract a problem?” Because it violates trust boundaries; your actor is inside the privileged walls, and is taking instructions from pretty much anyone on the outside. It also blends data and instructions, since agents have no concept of “just data” in the first place, down to their very perceptrons.

“Why is universality an interesting issue?” Because it causes users to blend credentials into a single place, a pretty big security hygiene violation. Credential boundaries are violated.

You can make that mapping with pretty much any new AI issue: the fundamentals haven’t changed, but the solutions need to.

I don’t believe these are insurmountable, but I do believe that we get better at thinking about and addressing them when we can name them.

We’ve named the Universal Open Contract. Let’s tackle it.