Feb 26, 2026·6 min read·56 visits
Mailpit versions prior to 1.29.2 contain a logic flaw in the 'Link Check' feature. The application fails to validate if a URL points to a local or private IP address before initiating a connection. This allows an attacker to use the Mailpit server as a proxy to scan the internal network (localhost, 10.x.x.x) or access cloud instance metadata (AWS/GCP), simply by sending an email containing a crafted link.
A critical Server-Side Request Forgery (SSRF) vulnerability in Mailpit's Link Check API allows unauthenticated remote attackers to map internal networks and enumerate cloud metadata. By injecting malicious URLs into emails and triggering the application's automated link verification, attackers can force the server to issue HTTP requests to arbitrary destinations, bypassing network segmentation.
Mailpit is the darling of the developer world. It is the modern, Go-based successor to MailHog—a tool designed to catch emails sent by your application during development so you don't accidentally spam your entire customer database while testing a password reset flow. It sits quietly in your staging environment or on your local machine, capturing SMTP traffic and presenting it in a nice web UI.
One of Mailpit's "helpful" features is the Link Checker. The idea is sound: developers want to know if the links in their marketing emails are broken before they go live. Mailpit parses the email, finds the anchor tags, and offers an API endpoint to verify them. It creates a convenient dashboard showing which links return 200 OK and which return 404 Not Found.
But here is the catch: to check a link, Mailpit has to visit the link. In security terms, the application acts as an HTTP proxy. If you don't strictly control where that proxy is allowed to go, you have just handed an attacker a skeleton key to your internal network. You built a tool to catch mail, but CVE-2026-27808 turns it into a tool to catch shells.
The vulnerability resides in internal/linkcheck/status.go. The function doHead() was responsible for performing the connectivity check. In earlier versions of Mailpit, the developers utilized Go's standard http.Client with a default transport configuration.
Go's default HTTP client is famously promiscuous. It does exactly what you ask it to do. If you ask it to fetch http://google.com, it goes to Google. If you ask it to fetch http://localhost:22, it tries to talk HTTP to your SSH daemon. If you ask it to fetch http://169.254.169.254/latest/meta-data/, it happily talks to the AWS metadata service.
The flaw wasn't a buffer overflow or a complex race condition; it was a lack of boundary validation. The code blindly trusted the URLs extracted from the email body. It failed to implement an "allowlist" of permitted schemes or a "blocklist" of private IP ranges (RFC 1918). It essentially treated user input (the email content) as a trusted command to initiate network traffic.
To fix an SSRF in Go, you cannot simply verify the URL string. Attackers can use DNS rebinding or alternative IP formats (e.g., 2130706433 for 127.0.0.1) to bypass string matching. The only robust way to fix this is to hook into the Dialer—the component that actually opens the TCP connection.
The patch in version 1.29.2 introduces a safeDialContext. Here is the logic breakdown of the fix:
1. Resolve First, Connect Second Instead of letting the HTTP client handle DNS, the fix manually resolves the hostname to an IP address first.
2. The Blocklist
The resolved IPs are checked against a strict list of forbidden ranges using a new IsInternalIP helper. This blocks Loopback (127.0.0.0/8), Private Networks (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12), and Link-Local addresses (169.254.0.0/16).
// Simplified view of the patch logic
func safeDialContext(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, _ := net.SplitHostPort(addr)
// Step 1: Resolve IP manually
ips, _ := net.DefaultResolver.LookupIPAddr(ctx, host)
for _, ip := range ips {
// Step 2: Check blocklist
if IsInternalIP(ip.IP) {
return nil, errors.New("blocked internal IP")
}
}
// Step 3: Dial the SPECIFIC VALIDATED IP, not the hostname
// This prevents DNS Rebinding attacks.
return dialer.DialContext(ctx, network, net.JoinHostPort(ips[0].IP.String(), port))
}3. Redirect Handling
The fix also includes a CheckRedirect hook. This is critical because a safe external URL (e.g., http://attacker.com/safe) could redirect to http://127.0.0.1/secret, bypassing the initial check. The patch ensures the safe dialer is used for every hop in the chain.
Exploiting this requires no authentication in default configurations. The attack vector works because Mailpit processes emails received via SMTP, and then exposes the Link Check results via HTTP API.
Phase 1: The Setup The attacker connects to the Mailpit SMTP port (usually 1025) or sends an email through an application that relays to Mailpit. The email body contains HTML with links targeting the internal infrastructure.
Subject: Trojan Horse
Check out these "harmless" links:
<a href="http://127.0.0.1:6379">Local Redis</a>
<a href="http://169.254.169.254/latest/meta-data/">AWS Metadata</a>
<a href="http://10.0.0.5:9200">Internal ElasticSearch</a>Phase 2: The Trigger The attacker queries the Mailpit API list endpoint to find the ID of the email they just sent. Then, they trigger the vulnerability:
GET /api/v1/message/{MESSAGE_ID}/link-check
Phase 3: The Oracle
Mailpit attempts to HEAD these URLs. The JSON response will contain the status of each link.
127.0.0.1:6379 returns a connection error, Redis isn't there.169.254.169.254 returns 200 OK, the attacker knows they are in a cloud environment and can proceed to enumerate role names and permissions.Because the API returns specific error messages and status codes, this acts as a high-speed internal port scanner.
While CVSS 5.8 might look "Medium," the contextual severity is often Critical. Mailpit is almost exclusively deployed in development and staging environments. These environments are notoriously lax on security.
1. Cloud Identity Theft: If Mailpit is running in AWS/GCP/Azure, this SSRF allows an attacker to steal the temporary credentials assigned to the instance. This often leads to full account compromise if the instance has over-privileged IAM roles (e.g., S3 full access).
2. Unauthenticated Internal Services: Developers often run databases like Redis, MongoDB, or Elasticsearch without authentication on localhost because "it's just dev." This vulnerability bridges the gap from the outside world directly to those open ports.
3. CI/CD Pipeline Poisoning: If Mailpit is running inside a CI pipeline, accessing the internal network might allow an attacker to interfere with build artifacts or steal source code.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Mailpit axllent | < 1.29.2 | 1.29.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-918 (Server-Side Request Forgery) |
| Attack Vector | Network (API Triggered) |
| CVSS v3.1 | 5.8 (Medium) |
| EPSS Score | 0.047% |
| Impact | Internal Reconnaissance / Metadata Exposure |
| Exploit Status | PoC Available |
Ruby Mechanize prior to version 2.14.1 contains an information disclosure vulnerability. When executing cross-origin HTTP redirects, global headers configured on the Mechanize agent (such as Authorization or Session Cookies) are dynamically re-applied to the subsequent request, bypassing the internal header-stripping logic. An attacker who controls a redirection endpoint can capture sensitive bearer tokens or cookies.
An origin trust boundary failure in the Ruby mechanize library (prior to v2.14.1) allows unauthenticated remote web servers to harvest sensitive global request headers, such as Authorization Bearer tokens and cookies, by utilizing HTML-level meta-refresh redirection tags. Standard HTTP-level redirect boundaries were not applied to document-level redirects, creating a vector for cross-origin credential leakage during automated crawls.
CVE-2026-107718 is a medium-severity Open Redirect vulnerability in the core HTTP server package of the AdonisJS Node.js framework. Prior to versions 8.2.3 and 9.3.0, the framework built route paths by directly interpolating dynamic parameters and wildcard segments without URI encoding. If an application routes attacker-controlled input directly to a dynamic first path segment and uses the generated route URL as a redirect destination, a leading slash can produce a scheme-relative external URL. Modern web browsers process scheme-relative URLs by redirecting the client to the specified external domain, exposing users to credential harvesting, social engineering, and session hijacking. This vulnerability affects all applications running unpatched configurations where input validation is not explicitly implemented before generating paths.
CVE-2026-107725 is a critical security bypass in Hazelcast where missing authorization checks in the MapPermission class permit unprivileged clients to issue queries containing aggregators or projections. This architectural oversight allows attackers to run arbitrary code on the cluster servers under the privileges of the active Hazelcast process.
A stored Cross-Site Scripting (XSS) vulnerability was identified in Indico, an open-source event management system developed at CERN, prior to version 3.3.13. The vulnerability stems from weak URL validation in custom link fields and lack of HTML sanitization during Marshmallow serialization of event notes. This allows authenticated attackers with event modification privileges to inject malicious payloads that execute in the browser of users viewing the event pages or collaborating on notes.
A technical analysis of CVE-2026-107397, a stored Cross-Site Scripting (XSS) vulnerability in Indico's collaborative notes editor and custom link generation fields. Prior to version 3.3.13, Marshmallow serialization schemas omitted HTML sanitization during conflict resolution, and form validators failed to enforce strict URI schemes, enabling authenticated low-privilege attackers to execute arbitrary JavaScript.