DEV Community

Carlos (Bloqarl)
Carlos (Bloqarl)

Posted on Originally published at zealynx.io AI-assisted

An MCP security checklist for developers wiring agents to real tools

Connecting an agent to a tool through MCP takes about ten minutes. You add a server to a config file, restart, and now the model can read files, run git, query a database or send email.

What you also did in those ten minutes is give a process that takes instructions from text it reads the same permissions as you. Every file the agent opens, every web page, every tool result is input that can steer it. And the MCP server on the other side usually runs with your user, your keys and your network.

Most of the MCP problems I've looked at are not exotic. They're the old bugs (path traversal, shell injection, hardcoded keys) in a place where the input now comes from a model that can be talked into anything. Plus a few new ones that only exist because of how agents read tool output.

So this is the list I'd go through before letting an agent use an MCP server on anything real.

The numbers, briefly

These are from public research in 2025, so treat them as a snapshot, not a census:

  • 82% of MCP servers studied use file operations prone to path traversal (Endor Labs).
  • 53% rely on static, long-lived credentials like hardcoded API keys (Astrix Security, 5,200+ servers analyzed).
  • 43% of servers tested had command injection (Equixly).

And the incidents that show the pattern:

  • Tool poisoning, April 2025. Invariant Labs showed a malicious MCP server, sitting next to a trusted WhatsApp server in the same agent, quietly exfiltrating chat history through instructions hidden in its tool description.
  • MCP Inspector RCE, June 2025. CVE-2025-49596, CVSS 9.4. Unauthenticated code execution on developer machines through Anthropic's official debugging tool.
  • mcp-remote, July 2025. CVE-2025-6514, CVSS 9.6. A malicious authorization_endpoint during the OAuth flow turned into OS command execution.
  • Postmark MCP, September 2025. A trojanized package on npm BCC'd every outgoing email to the attacker.

None of these needed a clever model attack. They needed one trust assumption nobody wrote down.

1. The server itself (4 checks)

These are the ones that end in code execution or file access on your machine.

  1. Path traversal. Every path argument is resolved and checked against the allowed root, after following symlinks. ../ is the obvious case, an absolute path or a symlink inside the allowed folder are the ones that slip through.
  2. Command injection. No shell strings built from tool arguments. Pass arguments as an array, and watch for arguments that start with -, because git log --grep="$input" stops being safe the moment the input becomes a flag.
  3. Execute primitives in "safe" tools. Git is the classic one. If the agent can write .git/config or pass -c options, it can register a hook, an alias or a filter that runs on the next commit or checkout. A reader pointed out on one of my earlier posts that git worktrees share hooks and config with the main repo, so a separate worktree doesn't help here either.
  4. Write boundaries. Writes are limited to the folders the task needs. Config files (.git/hooks, .husky, package.json scripts, CI files, editor settings) are persistence, so treat writes there as executions.

2. Tool poisoning and injection (4 checks)

The part that ordinary input validation doesn't catch.

  1. Injected instructions in tool output. A git log, a README, an issue comment or a web page can contain "ignore previous instructions". Assume it will. The defense is not a better prompt, it's that the agent can't do much damage when it follows one.
  2. Hidden content. Invisible Unicode, HTML comments and text the user never sees but the model does. Strip or flag it before it reaches the context.
  3. Exfiltration through normal-looking actions. A poisoned agent doesn't need a "send secrets" tool. A URL with a query string, an image link in markdown, a commit message, a search query. Check which of your tools can carry data out.
  4. Tool descriptions are input too. A server can put instructions in its own tool descriptions, or name a tool so it shadows one from a trusted server. Read the descriptions of every server you add, and re-read them when the package updates.

3. Credentials and access (3 checks)

  1. No hardcoded credentials. Use proper OAuth or short-lived tokens where the API supports it.
  2. Scoped keys. A key per server, with the smallest scope that works, rotated. Not your personal admin token in an env var that every server and every subprocess can read.
  3. Least privilege for the process. Don't run servers as root. Ideally not even as your main user: a container or a separate user with access only to what the task needs.

4. Several servers in one agent (3 checks)

  1. Combinations are the attack surface. A browser server plus a git server means web content can steer git operations. Filesystem plus network means read plus send. Look at what each pair can do together, not each one alone.
  2. Shared context. All servers write into the same model context, so one bad server can influence how the agent uses all the others.
  3. Write down the combinations. A short table of which servers are connected where, and what each combination could do if one of them went bad.

5. Supply chain (3 checks)

  1. Pin versions and verify what you install. An MCP server is a dependency with your permissions.
  2. Watch for typosquats and impersonation. The Postmark case was a package that looked like the real thing.
  3. Prefer a short list of servers you've reviewed over whatever a search turns up, and review updates like you'd review a PR.

6. Prompt and context (3 checks)

  1. Context pollution. Large or adversarial tool output can push your instructions out of the window or bury them.
  2. Keep instructions and data apart. Mark tool output as data in the way you build the context, and don't let it land in the system prompt.
  3. Size limits on what a tool can return.

7. Network and monitoring (4 checks)

  1. SSRF. Any tool that fetches a URL can be pointed at 169.254.169.254, localhost admin panels or your internal network. Block private ranges by default.
  2. Webhooks and callbacks are authenticated and can't be pointed anywhere by the model.
  3. Rate and spend limits that live outside the agent, so a loop or a poisoned plan can't run up a bill.
  4. Logs with arguments. Which tool was called, with what, by which agent session. When something goes wrong, "the agent did something" is not a useful incident report.

Before you ship it

  • Go through the OWASP Top 10 for Agentic Applications once with your setup in mind.
  • If the agent uses more than one server, review them together.
  • Test prompt injection against your actual model and tools, not a generic demo. Put an instruction in a file the agent will read and see what it does.
  • Decide what you'll do when a server or a key is compromised: how you revoke it, and how you find out what it touched.

The short version

If I could only pick three: no shell strings built from model input, no long-lived broad keys in the server's environment, and treat every tool result as something an attacker wrote. Most of the incidents above fail at least one of those.

What does your MCP setup look like? I'm curious how many people run servers in a container versus straight on their laptop.


Adapted from a guide our team published on zealynx.io.

I wrote this with help from AI for drafting and editing. The opinions and the audit experience are mine.

Top comments (3)

Collapse
 
slabb profile image
Sam LABBE •

Check 12 is the one that ages worst by hand — combinations grow O(n²) across servers and change with every install, so the table in your §4 is a document that's stale the week after it's written. That's where the receipt layer earns its keep: each tool call journaled with what it consumed makes the pair analysis a query instead of an audit document — "what could the browser server and the git server do together" falls out of the consumption edges rather than from someone re-reading the config.

And check 24 is where the checklist meets the receipts directly: "which tool was called, with what, by which session" is the right ask, but the custody grade decides whether that log is evidence or a diary. Same rule as your check 1 — config files are persistence, and a log the agent's user can edit is persistence too. Your line "they needed one trust assumption nobody wrote down" is the whole diagnosis; writing the assumption down at runtime, sealed, is what the receipt layer is for — the assumption becomes a record, and records can contradict the config.

Collapse
 
autenai profile image
Auten •

Good list. Disclosure: I'm on the Auten team, and we build a screen-control MCP server (it lets an agent click and type on a real computer and Android phone), so checks 5, 7 and 12 hit close to home.

For that class of server, "treat every tool result as something an attacker wrote" covers far more ground, because the tool result is the whole screen. Any email, chat or web page that happens to be visible is input. And the container answer from check 11 mostly doesn't apply, since the point of the tool is to act on your real desktop. So the boundary moves from the process to the action:

  • The skill we ship with the server tells the agent that screen text is data, never instructions.
  • The same skill tells the agent to ask the user before buying, deleting, sending a message, granting a permission or entering a password. Both are prompt-level rules, so they reduce the risk without removing it. Your point about limits that live outside the agent (check 23) applies here too.
  • Passwords are typed locally by the server from a saved login, so they never pass through the model context. That shuts one exfiltration path from check 7, though a poisoned plan can still type visible text somewhere it shouldn't.

On check 12, I'd add that a screen-control server collapses the combinations table: it can read anything the user can see and type into any app. It's effectively "filesystem plus network plus every logged-in session" in one tool, so it belongs on the short reviewed list, not something you add casually.

Collapse
 
cryptostrapon profile image
cryptostrapon •

The core of this is that an MCP server is a dependency running with your permissions, and the model turns old bugs like path traversal and shell injection into something anyone who can put text in front of the agent can trigger. One thing I would add to the multi server section is the approval step. If the confirm prompt only shows the tool name, a poisoned description can still get a harmless looking call approved, so it should show the final arguments and the resolved destination for anything that sends data out. Do teams actually re-read tool descriptions after the first install, or is that where drift usually slips in?