Safeguard
AI Security

The postmark-mcp Backdoor: What MCP Server Vetting Should Look Like

A trojanized MCP server BCC'd every email it sent to an attacker for weeks, downloaded 1,643 times, before anyone noticed. Here's the pattern and the fix.

Safeguard Research Team
Research
7 min read

On September 17, 2025, version 1.0.16 of an npm package called postmark-mcp shipped a one-line change that added a silent BCC of every email the server processed to phan@giftshop[.]club. The package had nothing to do with Postmark, the transactional email company whose name it borrowed — it was a third-party Model Context Protocol (MCP) server, published by an npm user named phanpak who had 15 earlier, clean versions of the package on the registry, plus more than 30 other packages under the same account. Security firm Koi Security discovered and disclosed the backdoor; by the time npm pulled the package, it had accumulated 1,643 downloads, and researchers estimated roughly 300 organizations were running the compromised server in production, each routing 10 to 50 emails a day through it. At peak, that's an estimated 3,000 to 15,000 emails a day quietly copied to an attacker-controlled inbox. It's widely cited as the first documented malicious MCP server found in the wild. The technique wasn't novel — it's a trojanized-update supply chain attack, the same pattern behind countless npm and PyPI incidents — but the target was new: the MCP servers that AI coding agents and assistants now install and grant broad tool-calling authority to, often with far less scrutiny than a production dependency gets.

What is an MCP server, and why does it carry more risk than a typical npm package?

An MCP server is a small process that exposes tools — send an email, query a database, read a file, call an API — to an AI agent over Anthropic's Model Context Protocol, introduced in November 2024. The risk difference from a normal dependency is authority and opacity. A compromised logging library can only do what your code calls it to do; a compromised MCP server is handed live credentials (an API key, an SMTP-sending permission) and then executes arbitrary logic against them on every invocation, often with no code review because it's configured once in an agent's settings file and never opened again. The postmark-mcp server needed the customer's own Postmark API key to function at all, which is precisely what made the backdoor effective: the attacker didn't need to steal credentials, they just needed the legitimate, already-authorized traffic to route through one extra BCC line. Because MCP adoption expanded quickly through 2025 as agentic coding tools became mainstream, teams were wiring these servers into production email, ticketing, and database systems faster than most security programs had a process to review them.

How did a fake package impersonate a real product long enough to gain trust?

Trust warm-up. The phanpak account published postmark-mcp looking like a legitimate community integration for Postmark's transactional email API, and let it sit clean through roughly 15 versions before landing the backdoor in v1.0.16. That gap matters: a static scan run against v1.0.1 would have found nothing, and most teams only scan a dependency once, at adoption time, not on every subsequent update. The same account controlled 30-plus other npm packages, a detail that in retrospect reads as classic sock-puppet infrastructure — volume and apparent longevity substituting for verifiable identity. Postmark, the real company, later published an advisory explicitly stating the package had no affiliation with them, but by then it had been indexed, recommended in community lists, and installed by hundreds of teams who reasonably assumed a package named after a product's brand was that product's own integration — the same naming-confusion logic that drives dependency-confusion and typosquatting attacks on traditional package registries.

Why did static description and README review fail to catch this?

Because the backdoor was a runtime behavior change, not a metadata problem. Anyone who read the postmark-mcp README or package.json in v1.0.16 would have seen nothing unusual — the change was a single added line inside the send-email code path, not a new dependency, new permission, or new field in the manifest. This is the core limitation of any vetting process that stops at "does the package look legitimate": it answers a question about intent and presentation, not about what the code actually does on this specific version. Detecting it requires either diffing behavior version-to-version — does this release open a new outbound network destination, or duplicate a recipient field, that the prior version didn't? — or genuine static/dynamic analysis of the artifact itself, run on every version, not just the one you installed six months ago. Teams that pin exact versions and never re-scan on update are exposed to exactly this timeline: safe today, silently compromised on the next npm update.

What should MCP server vetting and allow-listing actually require before adoption?

At minimum: verified publisher identity (does the account match the vendor's official GitHub org, not just a plausible name), a pinned and re-scanned version rather than a floating semver range, and a documented list of every tool the server exposes plus every credential it's handed — an MCP server that only needs to send email has no legitimate reason to also read the filesystem. Beyond that, egress should be constrained: if a server's only stated function is calling one vendor's API, its outbound network access should be limited to that vendor's domains, so a BCC-style exfiltration to an unrelated address is blocked at the network layer regardless of what the code does. Treat an MCP server addition the same as adding a new OAuth-scoped integration, not the same as npm install lodash — because the blast radius (every message an agent sends through it, indefinitely) is closer to the former.

How should ongoing monitoring differ from a one-time install-time check?

A one-time check only proves a package was safe at the moment you looked. The postmark-mcp timeline — 15 clean releases, then a compromised one — is the argument for scanning every subsequent version before it's pulled into an environment, and for re-scoring the corpus retroactively when detection models improve, since indicators that seem subtle in isolation (a new BCC field, a new outbound host) often only get flagged once enough similar incidents exist to compare against. This is also why version pinning without re-review is a false sense of safety: a pin protects against an accidental floating-range upgrade, but does nothing once a team deliberately bumps the pin to "the latest version" without diffing what changed in between.

How does Safeguard help against this class of risk?

Safeguard's Package Firewall and Eagle malware classifier apply directly to the class of risk postmark-mcp represents: an install-time proxy in front of npm and pip evaluates every fetch — including MCP servers pulled as ordinary npm packages — for typosquatting, namespace confusion, and behavioral indicators before code ever lands on disk, and it can run in audit, warn, or block mode as confidence in a new server grows. Eagle scores each version on install-script behavior, egress patterns, and credential-harvesting indicators, and explicitly treats "trust warm-up" — publishing several benign versions before a malicious one — as a named bypass pattern it watches for, rather than assuming a clean history means the newest release is clean too. Because Eagle re-scores the artifact corpus retroactively when its models improve, a server already installed months ago that gets reclassified due to a newly recognized signal surfaces as a finding shortly after the re-scan, instead of waiting for a manual audit of outbound mail logs to catch it. For teams standing up MCP-based agent tooling, that means the same install-time and continuous-monitoring discipline already applied to traditional dependencies extends naturally to the servers an agent calls, without treating each new integration as a one-time trust decision that's never revisited.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.