In September 2025, researchers at Koi Security found what they called the world's first sighting of a real-world malicious MCP server: an npm package named postmark-mcp that shipped fifteen clean versions to build legitimate adoption, then quietly added a single line of code in version 1.0.16 that blind-copied every email flowing through it to an attacker's inbox. The package was not Postmark's — the real Postmark MCP server lives on GitHub and had never been published to npm — and by the time it was caught, roughly 1,500 weekly installations were silently exfiltrating mail. The publisher never responded to the researchers. The package was simply deleted.
That is the whole incident in one paragraph. It is worth sitting with, because it compresses the entire MCP supply-chain problem into a shape small enough to hold in your head: a useful tool, a trusted name, a long clean history, and one version bump that turned all three into a lie. This post is a concrete accounting of what happened, what "it's been fine for fifteen releases" is actually worth against it, and what changes — both for teams installing third-party MCP servers and for platforms distributing their own — before a tool with deploy authority gets the same treatment.
Anatomy of the rug-pull
The attack was a slow-burn impersonation, and each stage exploited a different gap in how developers adopt MCP servers today.
| When | What happened |
|---|---|
| Pre-Sept 2025 | An npm package called postmark-mcp appears, copying the name of Postmark Labs' legitimate MCP server. It works as advertised: AI assistants can send email and manage templates through Postmark's API. Fifteen versions ship with no malicious code. |
| Sept 17, 2025 | Version 1.0.16 is published. A single added line BCCs every outgoing email to an address at an attacker-controlled domain. |
| Late Sept 2025 | Koi Security identifies the backdoor and reports the first-ever malicious MCP server spotted in the wild. |
| Days later | Contacted by researchers, the publisher does not respond — and deletes the package from npm instead. |
Three details make this worse than an ordinary typosquat. First, the name was not a near-miss like postmark-mcp-helper or post-mark-mcp; it was the exact name a developer would type after reading Postmark's own documentation, and Postmark confirmed it had never published its MCP server to npm at all, so there was no legitimate same-name package for the registry to prefer. Anyone who ran npx -y postmark-mcp got the attacker's code with no competing official artifact.
Second, the fifteen clean versions were the weapon, not the prelude. Each uneventful release accumulated download counts, stars, and "works for me" social proof — every signal a busy developer uses as a proxy for trust. Koi's CTO put the scale at roughly 1,500 weekly compromised installations and called the incident "a warning shot about the MCP ecosystem itself," not just one bad package. The backdoor did not need to fool anyone on day one. It needed the first fifteen versions to make version sixteen unremarkable.
Third, the payload was nearly invisible by design. Per Snyk's analysis, the malicious versions starting at 1.0.16 exfiltrated through a BCC to an address at the giftshop.club domain — one line, no new dependency, no network call to an obviously evil host in a dependency tree, no behavior change the user could see. Email sent through the server still arrived fine. The victim's copy and the attacker's copy differed by a header nobody renders. The Hacker News and BleepingComputer coverage both stress the same point: this passed as a routine patch bump, and routine patch bumps are exactly the updates nobody reviews.
Why "fine for fifteen releases" is worth nothing
The uncomfortable conclusion is that version history is not a trust signal at all. It is a familiarity signal, and familiarity is what the attacker farmed. Every defense developers actually rely on — "it's popular," "I've used it for months," "it's just a patch release" — measures the past, while the code that runs measures the present. The postmark-mcp timeline converts that abstraction into arithmetic: fifteen data points of good behavior had zero predictive power over the sixteenth release, because nothing in the distribution model bound the sixteenth release to the first fifteen except the same npm account holding the publish key.
The MCP ecosystem amplifies this in two ways that make it strictly worse than the analogous npm-package problem. The first is installation ergonomics: the canonical MCP server install is npx -y <package>, which resolves latest at every launch and auto-accepts whatever it finds. There is no lockfile moment, no npm audit gate, no human reading a diff — the agent host re-resolves and re-downloads on start. A rug-pull version does not wait for the victim to upgrade; the victim's next session picks it up automatically.
The second is who does the installing. MCP servers are disproportionately installed by agents themselves — a coding assistant told to "set up email sending" will happily add a server to its own config, and it evaluates candidates using exactly the signals the attacker gamed: name match, download counts, README quality. The human is two steps removed from a trust decision they would have made cautiously one step removed. When the tool being installed holds credentials — a Postmark API token in this case, a deploy token in the scarier version of this story — that distance is the vulnerability.
So the accounting the title promised: fifteen clean releases are worth exactly the assurance that fifteen specific tarballs were clean. They say nothing about the sixteenth, and any workflow that treats "update to latest" as safer than "install something new" has the risk backwards. Each published version is a new trust decision wearing an old name.
The same season's other warning: a CVSS 9.6 in the plumbing
postmark-mcp was a malicious server, but the same season produced the mirror image: trusted infrastructure with a hole a malicious server could drive through. CVE-2025-6514, disclosed by JFrog researcher Or Peles, was an OS command-injection flaw in mcp-remote — the widely used bridge that connects local MCP clients to remote servers — reachable through a crafted OAuth authorization_endpoint URL supplied by the server side. A malicious MCP server could use it for remote code execution on the connecting machine. CVSS 9.6. Hundreds of thousands of downloads in the blast radius. Fixed in version 0.1.16.
Read the two incidents side by side and the pattern is not "one bad package plus one bad bug." It is one disease with two symptoms: an ecosystem that trusted servers by default. The impersonation worked because victims trusted the registry to surface the real Postmark server and never checked the publisher; the vulnerable bridge worked the same way in code, trusting the server's OAuth metadata to be benign. Both assumptions were load-bearing, both were wrong, and both were fixed only after someone demonstrated the failure rather than before. That is the posture a deploy pipeline cannot afford: production access granted on the assumption that every server in the chain is both benevolent and bug-free.
The deploy-pipeline trust checklist
What follows is split deliberately in two, because "trust MCP servers" is two different jobs depending on which side of the publish button you sit on. Most teams do both: they install third-party servers into their agents and pipelines, and — if they run a platform — they distribute a server of their own that someone else's agent will trust the way victims trusted postmark-mcp.
For third-party servers you install, recommend, or bundle
- Pin
package@version, neverlatestor a bare name. Thenpx -y postmark-mcpinstall pattern is what turned a publish event into silent compromise. A pinned version converts "whatever the registry serves today" into a specific tarball you chose. - Commit a lockfile and verify it in CI. Pinning in a config file only works if drift gets caught; a checked-in lock record plus a CI step that fails on unexpected changes turns a rug-pull update into a blocked build instead of a quiet one.
- Verify the publisher before the first install, not after the incident. Check the npm registry page, the publisher identity, and whether the vendor's own docs point at that package — the postmark-mcp impersonation survived on developers skipping exactly this step. Prefer servers listed in the official MCP registry with verified publisher identity.
- Review the diff on every update; allow no silent auto-update path. If updating a server does not show a human (or a policy gate) what changed, you have re-created the exact conditions of v1.0.16. Treat a version bump of a credential-holding server with the seriousness of a credential rotation, because that is what it can amount to.
- Scan for known-bad packages and tool poisoning. Tools like
mcp-scanflag known malicious servers and detect post-install description changes; secret-scanning and prompt-injection checks on tool descriptions catch the subtler variant where the code is clean but the tool metadata carries hidden instructions. - Grant least privilege and require approval for sensitive tools. A server that only needs to send email should not also read files; a tool that deploys to production should require explicit per-call approval, not a blanket allow. Scope the credentials each server holds so one compromised server cannot spend another's authority.
- Sandbox what you do not fully trust. Run unfamiliar or community-maintained servers with restricted filesystem, network, and credential access, and expand the sandbox only as trust is earned per version — not per package.
For your own MCP distribution
If your platform ships an MCP server — especially one with deploy, rollback, or infrastructure authority — you are asking strangers to make the bet the postmark-mcp victims lost. Earn it structurally:
- Publish from pinned dependencies with reproducible builds. Your server's tarball should be rebuildable byte-for-byte from a tagged commit, so anyone can verify that what the registry serves is what your repo contains. An irreproducible build is an unauditable one, and "trust our CI" is the same familiarity argument the attacker used.
- Sign artifacts and attach provenance attestations. npm provenance, Sigstore signatures, SLSA-level build attestations — pick the mechanism your consumers can verify, and document the verification command. The goal is to make impersonation detectable: a same-name package without your signature should fail closed in a careful consumer's workflow.
- Version your tool contract, not just your code. Tool names, descriptions, and schemas are part of your API surface; agents cache assumptions about them. Version that contract explicitly so consumers can pin behavior, and never change tool semantics in a patch release.
- Give consumers no unreviewed auto-update path. Do not document
npx -y your-serveras the install method. Publish a changelog with security-relevant entries, support pinned installs as the default in your docs, and treat your own release process as adversarially interesting — because to your consumers' attackers, it is. - Hold production authority like a production credential. If your server can deploy, it holds the keys to production whether or not you call it that. Scope its default permissions to the minimum useful set, require step-up approval for destructive tools, log every privileged call with attribution, and rotate any embedded credentials on a schedule rather than on suspicion.
The through-line in both lists: replace familiarity with verification at every step where the postmark-mcp attacker substituted familiarity for trust. Pinning replaces "I know this package" with "I chose this tarball." Diff review replaces "it's just a patch" with "here is what changed." Provenance replaces "the vendor published this" with "this signature proves the vendor published this." None of these is exotic — they are the same controls the container and package ecosystems adopted after their own supply-chain fires. MCP is simply having its turn.
What "verified" has to mean next
The ecosystem response since September 2025 points in the right direction: an official MCP registry with publisher verification, secret scanning for MCP server configurations, OWASP's MCP security guidance and agentic-AI risk catalogs, and NSA design guidance treating servers as untrusted by default. The common thread is moving trust from vibes — names, stars, version counts — to cryptography and policy: verified publishers, signed artifacts, pinned versions, scanned configs.
But tooling only helps teams that change the default workflow, and the default workflow is still npx -y and hope. The postmark-mcp victims were not careless; they followed the documented happy path of an ecosystem that had not yet priced in its first rug-pull. A year on, the price is public: fifteen clean releases, one line of exfiltration, and every email in between. Any MCP server with production access — yours, your vendor's, the helpful community package your agent found on its own — deserves a trust story stronger than a download count. Build the checklist before the version bump, not after.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. As deploy authority moves into agent-operated MCP tools, the trust story for those tools is part of the platform. Star the repo on GitHub or deploy your first app today.



