How to Secure an MCP Server: A Builder's Hardening Checklist (2026 CVEs Included) — Cesar Ayala
← All posts

How to Secure an MCP Server: A Builder's Hardening Checklist (2026 CVEs Included)

To secure an MCP server: pin and audit package versions, bind to localhost with auth (never 0.0.0.0), treat every tool description and external data as untrusted, allowlist tools with least-privilege scopes, sandbox file and shell access, isolate tenants, and require human approval for any payment or customer-data action. MCP shipped ahead of its security story — default-deny.

Why is MCP suddenly a security problem in 2026?

MCP became a security problem because adoption outran its security story: more than 30 CVEs have been filed against the Model Context Protocol ecosystem in roughly 60 days since January 2026, including CVE-2025-6514 — a CVSS 9.6 remote code execution flaw in the mcp-remote package, which had 437,000+ downloads before disclosure.

That is not a slow trickle of edge cases. It is a protocol whose tooling shipped faster than its hardening, going from “interesting demo” to “default way agents touch the world” in about a year. CVE-2025-6514 is the first MCP vulnerability with documented mass-scale, real-world reach — a remote attacker getting code execution on the client just from the connection handshake.

Here is my opinion, labeled as such: MCP is genuinely powerful, and I use it in production. But the ecosystem shipped ahead of its security story. The moment you connect a server to an agent that can touch payments or fiscal data, you become the security boundary. Nobody else is going to draw it for you.

So this is not an MCP 101. If you want the basics, I cover what an AI agent is and how to connect an agent to your data and tools elsewhere. This post is the operational, do-this-today hardening checklist the launch blogs and vendor docs skip.

CVEs in ~60 days30+ filed against the MCP ecosystem since Jan 2026
mcp-remoteCVE-2025-6514 — CVSS 9.6 RCE, 437k+ downloads
MCPJam InspectorCVE-2026-23744 — CVSS 9.8, 0.0.0.0 + no auth, zero-click RCE
File-op servers82% vulnerable to path traversal (scan of 2,614)

What are the worst real MCP vulnerabilities so far?

Three incidents are worth knowing by their exact IDs, because they map directly to mistakes you can avoid.

mcp-remote — CVE-2025-6514 (CVSS 9.6). A malicious MCP server can embed a command during the connection and authorization phase that then executes on the client OS. It affects versions 0.0.5 through 0.1.15 and is fixed in 0.1.16. The scary part: full system compromise from simply connecting to an untrusted server. You did not run anything yourself — the handshake did it for you.

MCPJam Inspector — CVE-2026-23744 (CVSS 9.8). Versions 1.4.2 and earlier listen on 0.0.0.0 with no authentication on a critical endpoint, and the fix landed in 1.4.3. A crafted HTTP request can install MCP servers and execute arbitrary code with zero user interaction. This is the classic “I’m only running it locally” trap — except a 0.0.0.0 bind is not local, it is every interface on your network.

OX Security’s systemic disclosure. OX Security disclosed a command-injection vulnerability at the core of MCP tooling spanning 150M+ downloads, exposing sensitive user data, internal databases, API keys, and chat histories. (For the RCE mechanism across the wider ecosystem, see OX Security’s related supply-chain advisory.)

Notice the pattern. None of these are exotic crypto breaks or nation-state zero-days. They are unauthenticated endpoints, code that trusts server-supplied input, and unpinned supply chains. Boring, fixable mistakes — which is exactly why a checklist works.

How bad is it at scale — what do the scans show?

If the named CVEs were the whole story, you could just patch three packages and move on. The scans say otherwise.

A scan of 2,614 MCP implementations found that 82% of those handling file operations were vulnerable to path traversal — the “../../etc/passwd” class of bug that should have died a decade ago. In the same body of work, 67% of scanned implementations carried code-injection risk. And of 518 officially registered MCP servers, between 38% and 41% offered no meaningful authentication at all (518-server registry audit, early 2026).

Sit with that last number. Roughly four in ten registered servers — the ones with a stamp of legitimacy — have no real auth. The base rate of broken authentication and unsandboxed file access is too high to extend trust by default.

The operational takeaway is simple: assume a server is vulnerable until you have verified otherwise. You are not being paranoid; you are matching the measured base rate.

What are the 5 attack-pattern families (and the defense for each)?

Before the checklist, you need the mental model. Every MCP attack I have seen falls into one of five families, and each maps to a specific defense.

Tool poisoning. Malicious instructions hidden in a tool’s description or JSON schema steer the agent toward actions you never authorized. The defense: treat all tool descriptions as untrusted input. Tool metadata is data, not a command — never let it silently drive actions.

Prompt injection via external data. Attacker-controlled text inside fetched web content or another tool’s output hijacks the agent mid-task. The defense: never let tool output auto-execute a high-stakes action. Keep a hard trust boundary between “content the agent read” and “actions the agent takes.”

Trust bypass. Unauthenticated endpoints, 0.0.0.0 binds (the MCPJam pattern), and silently re-approved configs — Cursor IDE’s MCPoison issue (CVE-2025-54136) re-executed an approved MCP config after silent modification, with no re-approval. The defense: localhost plus auth on every server, no exceptions, and re-verify configs on change.

Supply-chain attacks. A compromised or typosquatted package — the mcp-remote and 150M-download patterns. The defense: pin and audit versions.

Cross-tenant exposure. One tenant reads or acts on another tenant’s data. The defense: hard tenant isolation and scoped credentials. Real-world MCP incidents span these families across WhatsApp MCP, GitHub MCP, Cursor IDE, and Anthropic’s own servers — nobody is exempt.

Attack family

  • Tool poisoning — hidden instructions in a tool description
  • Prompt injection — attacker text in fetched data or output
  • Trust bypass — unauthenticated endpoint, 0.0.0.0 bind, or silently re-approved config
  • Supply chain — compromised or typosquatted package
  • Cross-tenant — one tenant reads or acts on another's data

Defense

  • Treat tool descriptions as untrusted; metadata never drives actions
  • No auto-execute on tool output; keep a trust boundary
  • Localhost plus auth on every server; re-verify configs on change
  • Pin and audit versions before bumping
  • Hard tenant isolation and scoped credentials

The hardening checklist: what do I lock down, in order?

This is the core of the post. Order matters — supply chain first because it is the layer you control before anything even runs.

  1. Pin and audit MCP package versions. No floating ranges. A ^ or ~ range will happily auto-pull a future compromised release. Pin exact versions and read the changelog before you bump.
  2. Never bind to 0.0.0.0 — localhost plus auth. This applies to servers and inspectors. Require authentication on every server, not just the public-facing ones. The MCPJam CVE was exactly this mistake.
  3. Treat every tool description and all external data as untrusted. Tool poisoning and prompt injection both live here. Do not let tool output silently drive actions.
  4. Allowlist tools and grant least-privilege scopes. Default-deny, then enable per tool. An agent should not have access to a capability just because the server exposes it.
  5. Sandbox and contain file and shell access. Chroot, container, or jailed filesystem — no raw access to the host. This is your answer to that 82% path-traversal rate.
  6. Isolate tenants and scope credentials so no single server can cross-read another tenant’s data.
  7. Require human-in-the-loop for high-stakes actions — anything touching payments, refunds, or customer and fiscal data.

Two small sketches make the early steps concrete. First, bind to localhost with auth, never 0.0.0.0:

// mcp-server.config.jsonc — illustrative
{
  "host": "127.0.0.1",        // localhost ONLY — never "0.0.0.0"
  "port": 8731,
  "auth": {
    "required": true,          // reject every unauthenticated request
    "scheme": "bearer"
  }
}

Second, a default-deny tool allowlist with scoped privileges:

# tool-allowlist.yaml — illustrative
default: deny
tools:
  read_invoice:
    allow: true
    scopes: ["invoices:read"]      # read-only, nothing else
  issue_refund:
    allow: true
    scopes: ["payments:refund"]
    require_human_approval: true   # high-stakes -> human gate

If you are migrating servers to the latest protocol, fold this checklist into that work — see the 2026 MCP spec migration.

  1. 1. Pin and audit versionsNo floating ranges; read the changelog before bumping
  2. 2. Localhost plus authNever bind to 0.0.0.0; require auth on every server
  3. 3. Untrusted tools and dataTool descriptions and external data never auto-drive actions
  4. 4. Allowlist plus least privilegeDefault-deny, enable per tool with minimal scopes
  5. 5. Sandbox file and shellChroot or container; no raw filesystem access
  6. 6. Isolate tenantsScoped credentials so no server can cross-read
  7. 7. Human-in-the-loopRequired for payments, refunds, and fiscal data

What does a hardened request actually look like?

The checklist becomes real when you trace a single request through it. Every hop is a checkpoint, not a formality.

An agent issues a call. It hits the allowlist gate first — if the tool is not explicitly enabled, it never runs. If it passes, it executes inside a sandboxed scope where file and shell access are contained, so a path-traversal attempt has nowhere to go. The server then enforces an auth check, rejecting anything unauthenticated. And if the action is high-stakes — a refund, a write to customer or tax data — it pauses at a human approval gate before anything moves.

Each checkpoint blocks a specific failure: an unlisted tool never runs, a sandboxed tool cannot path-traverse, an unauthenticated call is rejected, and a refund pauses for a person.

The payments tie is where I am least flexible. In my own Stripe and LATAM payments work, the human-in-the-loop gate is non-negotiable. An agent that can issue a refund or read a customer’s RFC and tax data is one prompt injection away from a chargeback or a data-leak incident. I would rather add a click than explain a breach.

Be honest about the limit, though: this hardening reduces risk, it does not eliminate it. A determined supply-chain compromise or a genuinely novel injection can still slip a layer. That is the whole point of layering — so that one bypassed control is not the end of the story. Do not rely on any single defense.

Agent issues callRequest enters the hardened path
Allowlist gateUnlisted tool never runs (default-deny)
Sandboxed scopeFile and shell contained — no path traversal
Auth checkUnauthenticated calls rejected at the server
Human approvalRefunds and fiscal writes pause for a person

MCP security FAQ

Is it safe to connect to a public MCP server? Treat it like running untrusted code, because that is what it is. Pin the version, sandbox it, and never hand it payment scopes without a human gate. CVE-2025-6514 proved a malicious server alone can own your machine from the handshake.

Do I really need auth on a localhost server? Yes. The MCPJam CVE-2026-23744 pattern (CVSS 9.8) was a 0.0.0.0 bind with no auth — which exposes every network interface, not just your loopback. Localhost-only plus auth closes both holes.

Does pinning versions actually help? It is your main supply-chain defense. Unpinned ranges auto-pull a future compromised release without you doing anything. Pin exact versions, then audit before bumping.

Can structured outputs or schemas make a tool safe? No. Schemas constrain shape, not intent — a poisoned tool description still steers the agent even if its output is valid JSON. Validate behavior, not just the payload.

I’m not a contador — where’s the fiscal line? As an engineer, my rule is to gate any action touching tax or customer data behind a human. Do not let an agent autonomously write fiscal records. I draw the engineering boundary; your accountant owns what is correct inside it.

The bottom line

MCP shipped ahead of its security story. Connect deliberately, default-deny, and put a human in front of anything that moves money or touches fiscal data. For more on building with these tools, see my AI agents guides.

Run the checklist before your next server connection — not after the incident.