Fail-Safe Defaults for MCP Server Permissions
Deny-by-default architecture closes permission gaps that leave most MCP servers wide open.

I've lost count of how many MCP server audits I've sat in on where the first finding is the same: nobody locked the front door. Bloomberry's research, the version Palo Alto Networks cites most often, puts it at 38%. That's the share of MCP servers running with zero authentication, not weak authentication. None.
The other 88% that do ask for some kind of credential aren't exactly a relief either. Astrix Research dug through more than 5,200 open-source MCP implementations and found 53% leaning on static, long-lived secrets, API keys, Personal Access Tokens, the stuff you generate once and then forget exists. OAuth, mature and battle-tested for over a decade now, shows up in only 8.5% of them. Meanwhile, 79% of those API keys travel through environment variables, which offer no encryption and no built-in expiration. You set it, it sits there, and unless somebody specifically remembers to kill it, it just stays valid. Nobody remembers.
The vulnerability scans tell the same story from a different angle. Enkrypt AI ran 1,000 servers through testing in October 2025 and turned up critical vulnerabilities in a third of them. Equixly found command injection flaws in 43% of what they tested between late 2025 and February 2026. Endor Labs checked 2,614 implementations and found 82% using file operations vulnerable to path traversal, while BlueRock scanned over 7,000 servers in 2026 and clocked SSRF issues in 36.7%.
None of that adds up to a bell curve with a few bad apples on one end. It's a center of gravity sitting on "insecure," where the well-configured server is the exception, not the rule. A May 2026 automated scan across tens of thousands of real-world MCP repos found 106 zero-days, which rules out the comforting theory that this is just some long tail of abandoned side projects. The OpenClaw incident that January turned up vast numbers of unauthenticated endpoints, sitting wide open, leaking API keys, Slack credentials, entire chat histories, all in public view. Nobody was hiding anything, because nobody thought to.
How permissive defaults translate into the four main attack classes
Four patterns keep showing up once you trace how these exposures actually get used against you.
Start with over-privileged access, which usually shows up as the confused deputy problem in practice. Hand an agent broad file system, network, and shell access, and you've built a blast radius before anyone's even done anything malicious. MCP's tool-chaining makes it worse: one over-privileged grant cascades into shell commands or straight exfiltration. A user with entirely legitimate access to a server can invoke tools well past what anyone intended for them, and server-level controls do nothing to stop it. You need scoping at the tool level, because agents move at machine speed. A misconfigured permission doesn't wait around; it scales the damage immediately.
Tool poisoning is the sneaky one. Malicious instructions get buried in a tool's metadata and hijack the model's decision-making before the user's done anything at all. Invariant Labs showed this off in April 2025 with a poisoned math tool that quietly read and exfiltrated SSH keys, no clicks, no trace. Around 5.5% of open-source MCP servers analyzed in 2025 showed signs of this. The deeper problem is that most clients validate a tool's definition exactly once, at approval, and never again. Change the definition afterward and the agent just keeps trusting it. That's the rug pull, and it works precisely because nobody's watching after the handshake.
Then there's implicit trust across chained servers, which is really a trust-propagation failure. In a multi-server pipeline, a low-privilege server can return text that nudges the agent into calling a privileged tool on a completely different server, one the operator never authorized for that specific action. A January 2026 arXiv paper, "Breaking the Protocol," named the pieces directly: implicit trust propagation, no capability attestation, bidirectional sampling with no origin authentication. Context moves between servers with nobody checking where it came from.
Last is insecure network binding, the one that sounds boring until you see what it enables. Bind a server to 0.0.0.0 and suddenly any device on that network reaches it, no login required. Researchers have started calling this the NeighborJack condition. CVE-2025-49596, filed against Anthropic's MCP Inspector with a CVSS of 9.4, chained DNS rebinding with exactly this binding mistake to get remote code execution. This isn't some exotic attack path requiring a nation-state budget. It's the predictable outcome of default network exposure nobody ever went back and fixed.
What connects all four: every one of them needs the server or agent to be holding access it was never explicitly handed. Take that precondition away, and there's no exploit path left to walk down.
What deny-by-default actually means as a permission architecture
Here's the principle, stated as plainly as I can: nothing, no agent, no tool, no server, gets any access unless someone specifically granted it. Silence isn't a gray area. Silence is a denial.
Deny-by-default isn't about restricting capability for the sake of restriction. It's about making sure any access that does exist is there because someone decided it should be, not because nobody got around to blocking it.
That decision plays out at three layers, and you need all three or the whole thing leaks. Server level: can this agent even connect here. Tool level: once connected, which specific tools can this role actually call. Parameter and operation level: within a tool it's cleared to use, which operations, read versus write versus delete, against which data. Skip the tool layer and you've rebuilt the confused deputy problem inside the very system meant to close it. Skip the operation layer and a tool grant alone still lets someone delete something nobody authorized.
None of this holds together without identity underneath it. You can't enforce a denial against a principal you can't verify, so every agent and every server needs a checkable identity before any policy actually means anything. A server with no registry entry isn't a server the policy is lenient toward; it's a server the policy literally cannot see. There's no name there to deny.
Compare that to the alternative, open unless explicitly blocked. Under that setup, the burden flips onto the operator, who now has to anticipate and pre-block every possible misuse before it happens. That's a losing bet against agents that can generate novel misuse faster than any rulebook keeps pace. The NSA said as much in its May 2026 guidance: MCP's adoption has flat-out outrun its safeguards. Deny-by-default is the fix applied at the foundation, not stitched on after the exposure's already made headlines.
How RBAC and least-privilege grants translate deny-by-default into operational rules
Deny-by-default is the principle. RBAC is how you actually run it day to day. Roles define categories of access, and grants attach to roles rather than to individual agents or individual people, which keeps the system from turning into a graveyard of one-off exceptions nobody remembers approving.
Roles should map to intent, not identity. A "read-only analytics agent" role gets exactly the tools and parameters that job calls for and nothing else, regardless of which model happens to be sitting behind it that week.
In practice, least-privilege grants get narrow fast:
- Scoped to individual tools, never blanket server access
- Scoped to specific operations, read-only rather than read-write-delete bundled as one package
- Scoped to particular data objects or namespaces, not a whole file system or database
- Time-bound, so the grant expires when the task does
Take a delete-contact tool on a CRM server. A user with general server access should hit a wall there unless that exact operation is written into their role. Server-level access doesn't cut it; the check has to happen at the operation itself, every time.
This is also where OAuth pulls ahead of static API keys, and it's worth dwelling on for a second. Short-lived tokens expire by design. A static key just sits there, valid indefinitely, until somebody remembers to revoke it, and that revocation rarely happens on any kind of schedule. Astrix found OAuth adoption at 8.5% across MCP implementations in 2025. The distance between what's considered best practice and what teams actually ship is where most of the real exposure sits, and it's not close.
None of this is a new idea, honestly. AWS IAM has treated services as scoped principals for years now, and applying that same discipline to MCP agents, treating each one as a principal with a tight, specific role, is just extending something enterprises already trust for people and services to a new kind of actor. Agents run at machine speed, though, so a scoping mistake compounds faster here than the same mistake would with a human on the other end. RBAC for agents is how you make sure capability got authorized on purpose.
Tool validation and re-authentication as the runtime enforcement of the default
Most MCP clients check a tool's definition exactly once, at approval, and never revisit it. That's the whole gap the rug pull lives in. Change the definition after the fact, and the client just keeps trusting it like nothing happened.
Closing that gap at invocation time takes a few things running continuously, not as a one-time check:
- Re-validating tool definitions against a trusted baseline on every call, not leaning on a cached approval from three sessions ago
- Catching drift, so any change to a tool's metadata since approval triggers a re-review or an automatic denial
- Confirming the tool actually invoked matches what was authorized, name, version, capability signature, the whole fingerprint
CVE-2025-6514 is the one I bring up most when I explain this to people. The mcp-remote package, downloaded over 437,000 times, let a client achieve full remote code execution on the host just by connecting to an untrusted remote server, because the client took the server's endpoints at face value without verifying anything. CVSS 9.6, a validation failure at connection time, a different animal from an RBAC failure, which is exactly why the two controls need to run side by side rather than as substitutes for each other.
Input validation on the server side carries just as much weight. Equixly's 43% command injection rate tells you the absence of sanitization is closer to the norm than the exception right now. File-operation tools need real path canonicalization too. CVE-2025-53110 and CVE-2025-53109 both proved that a naive string-prefix check does nothing to stop path traversal; it just looks like it does.
In a multi-server pipeline, a task handed from one server to another should carry where it came from and what scope it's allowed under. If server A tells the agent to go call a tool on server B, server B needs to treat that instruction as unverified until it checks the authorization itself, independently. Deny-by-default isn't a gate you pass through once at the start. It's a posture you reapply at every decision point along the way.
Why real-time observability is what makes deny-by-default enforceable rather than theoretical
You can write the tightest deny-by-default policy in the world and still have no idea whether it's holding up once real traffic hits it. That gap, between the policy on paper and what's actually enforced at runtime, is where most of the damage happens in practice.
Real observability means a few concrete things: a record of exactly which agent called which tool, with what parameters, against what data, and when; catching a tool's definition changing between sessions before it gets exploited, not after; and an alert firing the moment an agent reaches outside its granted scope, while it's happening, not a line item in a log somebody reviews next Tuesday.
That timing distinction matters a lot more than it sounds like it should. An audit log tells you what already happened, but it doesn't stop anything and doesn't contain anything. It's useful for the postmortem, sure, but a postmortem teaches you about the last incident. It does nothing for the one happening right now.
The GitHub MCP prompt injection from May 2025 is the case that makes this concrete. An agent fetching open issues ended up quietly exfiltrating data from a private repo as a side effect, with no user interaction, no error thrown anywhere, no signal to anyone until well after the fact.
Real enforcement needs visibility at the tool-invocation level, not just at the point where a connection to a server first opens. It needs anomaly detection, so a read-only role attempting a delete surfaces in the moment, not three days later during a log review nobody wanted to do. It also needs session-level context, since the exact same call can be routine or a five-alarm fire depending entirely on what happened earlier in that session.
The systemic RCE that OX Security disclosed in April 2026, hitting every major MCP SDK across Python, TypeScript, Java, and Rust, is a good reminder that even a well-governed shop can inherit a flaw baked into the protocol itself, one that has nothing to do with how carefully you configured your own servers. Real-time observability is what turns that kind of inherited flaw into something you catch mid-flight instead of something you read about after the fact in a disclosure report. Visibility only does its job while the action is unfolding. After that, it's just a record of what you missed.


