5.8
Medium
github.com/axllent/mailpit

CVE-2026-55187

CVE-2026-55187 is a medium-severity server-side request forgery (SSRF) vulnerability in github.com/axllent/mailpit (go), affecting versions <= 1.30.1. It is fixed in 1.30.2.

Key facts
CVSS score
5.8
Medium
Attack vector
Network
Issuing authority
GitHub Advisory Database
Affected package
github.com/axllent/mailpit
Fixed in
1.30.2
Disclosed
2026

Summary

Summary The remediation shipped in mailpit v1.29.2 for GHSA-mpf7-p9x7-96r3 (CVE-2026-27808) is incomplete. The tools.IsInternalIP deny-list relies on Go's stdlib classification helpers (IsLoopback, IsPrivate, IsLinkLocalUnicast, IsLinkLocalMulticast, IsUnspecified, IsMulticast) plus an inline CGNAT range, but those helpers do not match two classes of IPv6 address that should be blocked for SSRF purposes: IPv6 forms that embed an IPv4 destination via documented translation mechanisms, 6to4, NAT64, IPv4-compatible IPv6, ISATAP, or (in older Go versions) IPv4-mapped IPv6. These let an attacker reach internal IPv4 destinations by supplying an IPv6 literal that encodes the desired IPv4. IPv6 prefixes that fall outside the narrow private/loopback/link-local ranges Go's stdlib classifies, specifically the deprecated site-local prefix fec0::/10 (RFC 3879/4291) and the documentation prefix 2001:db8::/32 (RFC 3849). The first is still routable on dual-stack hosts and is cited as a bypass form in CVE-2026-44430; the second should never appear in real network traffic and is safe to block as fail-safe behavior. Together these gaps let the Link Check API be coerced into dialing internal destinations that the v1.29.2 fix was intended to block. This is the same bug class as GHSA-56c3-vfp2-5qqj / CVE-2026-44430 (MCP Registry) and GHSA-86m8-88fq-xfxp / CVE-2026-45741 (Gotenberg), projects that, like mailpit, built their SSRF deny-list around Go's stdlib Is family and discovered the resulting bypass post-disclosure. The underlying ecosystem-wide issue is tracked upstream at golang/go#79925, which proposes extending net.IP.IsPrivate to handle these IPv6 transition forms. Until that lands, every Go project that wants comprehensive SSRF protection has to implement the decoding itself, which is exactly the gap that produced this advisory and the three CVEs in adjacent projects cited above. Affected versions mailpit v1.29.2 and later HEAD, the GHSA-mpf7-p9x7-96r3 fix is in place but tools.IsInternalIP does not cover the IPv6 forms enumerated below. Pre-v1.29.2 versions remain vulnerable to the original advisory. Vulnerable code internal/tools/net.go L25-L34, IsInternalIP: internal/linkcheck/status.go L140-L163, safeDialContext calls IsInternalIP on resolved IPs before dialing, but only blocks when one of the seven predicates above fires. For each of the following bypass forms, net.IP.IsLoopback, IsPrivate, IsLinkLocalUnicast, IsLinkLocalMulticast, IsUnspecified, IsMulticast, and the CGNAT range check all return false, so the dial proceeds: IPv4-embedded-in-IPv6 forms (each carries an IPv4 destination via a documented translation prefix): | Bypass IPv6 literal | Decoded IPv4 destination | RFC | |---|---|---| | 64:ff9b::a9fe:a9fe | 169.254.169.254 (AWS / GCP / Azure metadata) | RFC 6052, NAT64 well-known prefix | | 64:ff9b:1::a9fe:a9fe | 169.254.169.254 | RFC 8215, NAT64 local-use | | 2002:a9fe:a9fe:: | 169.254.169.254 | RFC 3056, 6to4 | | ::a9fe:a9fe | 169.254.169.254 | RFC 4291 §2.5.5.1, IPv4-compatible IPv6 | | 64:ff9b::7f00:1 | 127.0.0.1 | RFC 6052 (loopback via NAT64) | | 2002:0a00:0001:: | 10.0.0.1 | RFC 3056 (RFC 1918 via 6to4) | | <any-prefix>:5efe:<ipv4> | <ipv4> (e.g. 2001:db8::5efe:7f00:1 → 127.0.0.1) | RFC 5214, ISATAP | Direct IPv6 prefixes not classified by the stdlib Is family: | Bypass IPv6 literal | What it is | RFC | |---|---|---| | fec0::1 (any address in fec0::/10) | Deprecated site-local, still routable on dual-stack hosts | RFC 3879 (deprecation) / RFC 4291 §2.5.7 | | 2001:db8::1 (any address in 2001:db8::/32) | Documentation prefix, should never appear on the wire | RFC 3849 | IsInternalIP returns false for every entry in both tables. The original advisory's stated mitigations do hold against the embedded-IPv4 forms in the narrow case where the IPv6 literal is ::ffff:<ipv4> (IPv4-mapped), because Go's net.IP.To4() normalizes that form and the stdlib Is methods then check the embedded IPv4. This was the partial fix shipped in Go 1.22.4 / CVE-2024-24790. But it does not extend to 6to4, NAT64, IPv4-compatible, or ISATAP forms, those require explicit decoding that neither Go's stdlib nor IsInternalIP performs. The direct prefixes (fec0::/10, 2001:db8::/32) likewise are simply outside the scope of any Go stdlib Is method. Proof of Concept The repro depends on environment-specific routing for the embedded IPv4 destination. The forms below all pass the safeDialContext check on a stock mailpit v1.29.2, they will not be blocked by the SSRF deny-list. Whether they connect successfully depends on whether the host's network has NAT64 / 6to4 routing to reach the embedded IPv4. Unit-test repro (no network dependency) The most defensible PoC is a unit test against IsInternalIP itself, it demonstrates the deny-list gap directly without depending on the test environment routing the bypass IPs: Run with: On v1.29.2 every subtest fails. Each failure is a documented bypass. End-to-end repro In an environment where the embedded IPv4 destination is reachable (e.g. a host whose network provides NAT64 to RFC 1918 / link-local): Send a crafted email to mailpit's SMTP listener containing an <a href> with a bypass URL: html <a href="http://[64:ff9b::a9fe:a9fe]/latest/meta-data/iam/security-credentials/">link</a> POST /api/v1/message/{ID}/link-check. Observe the doHead HTTP HEAD response status, non-zero status (success or specific error) confirms the dial reached the destination rather than being blocked by IsInternalIP. In environments without NAT64 / 6to4 routing the connection will time out, but the absence of a private/reserved address blocked response confirms the deny-list bypass logically; the unit test above is the canonical PoC. Impact Identical scope and severity model to the original GHSA-mpf7-p9x7-96r3: The link-check API is reachable in mailpit's default deploy without authentication (no --ui-auth, no --smtp-auth required). An attacker who can deliver email to the mailpit SMTP listener (often unauthenticated in default config) and invoke the link-check API can probe internal services using any of the uncovered IPv6 forms above, either via the embedded-IPv4 mechanisms to reach IPv4 destinations like cloud metadata endpoints (169.254.169.254, 168.63.129.16), or by addressing a routable IPv6 service via fec0::/10 directly. The status-code-and-error feedback exposed by the link-check API leaks reachability information per probe. Damage ceiling is bounded by the mailpit response shape (status code, status text, 451 Blocked private/reserved address sentinel), no response body is exposed, but reachability + status-code mapping is sufficient for service discovery and for confirming cloud-metadata service identity. Scope note: tools.IsInternalIP is also used by the screenshot-proxy and HTML-Check-API endpoints (per maintainer disclosure). The same deny-list bypass applies to dialer decisions in those paths, but they include additional checks that mute the impact. The Link Check API remains the most revealing because its response includes the HTTP status code from the dialed destination; the other two are less directly leaky. Severity: Moderate, mirroring the original advisory (CVSS 5.8). Suggested remediation The fix has two parts: For the IPv4-embedded-in-IPv6 forms: decode the embedded IPv4 and re-check it. This is the same pattern Python's ipaddress.isprivate implemented in 3.13, what code.dny.dev/ssrf (IANA Special Purpose Registry-driven, auto-synced) implements out-of-the-box, and the behavior change being proposed for Go's stdlib at golang/go#79925. For the direct IPv6 prefixes: add them to the first range check alongside cgnatRange.Contains. Reference implementation (extends the existing helper, keeps the call-site contract identical): This covers every bypass in the two tables above. The direct-prefix additions (deprecatedSiteLocal, documentationPrefix) are two lines in the first if-block; the embedded-IPv4 decoder is the substantive new function. Alternative, adopt a comprehensive library: Replace the hand-rolled deny-list with code.dny.dev/ssrf, which generates its IPv4 and IPv6 prefix lists from the IANA Special Purpose Registries via a bi-monthly auto-sync. This protects against future RFCs adding new transition forms without requiring further mailpit maintenance. References Original advisory: GHSA-mpf7-p9x7-96r3 / CVE-2026-27808 Vulnerable function: internal/tools/net.go#L25-L34, IsInternalIP Caller: internal/linkcheck/status.go#L140-L163, safeDialContext Upstream Go-stdlib issue tracking the root cause: golang/go#79925, proposal to extend net.IP.IsPrivate semantics and improve documentation Related: same bypass class in other Go projects, GHSA-56c3-vfp2-5qqj / CVE-2026-44430, GHSA-86m8-88fq-xfxp / CVE-2026-45741 Go stdlib design context: Damien Neil's comment ("IsPrivate was a mistake. Just about every use of it that I've seen seems to misuse it.") Python stdlib reference: ipaddress.isprivate 3.13 docs, covers 6to4, NAT64 explicitly Comprehensive Go library: code.dny.dev/ssrf, IANA-registry-driven RFCs: 3056 (6to4), 4380 (Teredo), 6052 (NAT64), 8215 (NAT64 local-use), 4291 (IPv6 addressing including IPv4-mapped/compatible), 5214 (ISATAP), 3879 (site-local deprecation), 3849 (documentation prefix)

Impact

What is server-side request forgery (SSRF)?

Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside. Typical impact: access to internal metadata services, internal APIs, or cloud credentials.

Severity and exposure

CVE-2026-55187 has a CVSS score of 5.8 (Medium). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment.

A fixed version is available (1.30.2). Upgrading removes the vulnerable code path.

Affected versions

go

  • github.com/axllent/mailpit (<= 1.30.1)

Security releases

  • github.com/axllent/mailpit → 1.30.2 (go)
Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter instead of chasing every advisory.

Kodem's runtime-powered SCA identifies whether CVE-2026-55187 is reachable in your applications. Explore open-source security for your team.

See if CVE-2026-55187 is reachable in your applications. Get a demo

Remediation advice

Upgrade github.com/axllent/mailpit to 1.30.2 or later to resolve this vulnerability.

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently asked questions about CVE-2026-55187

What is CVE-2026-55187?

CVE-2026-55187 is a medium-severity server-side request forgery (SSRF) vulnerability in github.com/axllent/mailpit (go), affecting versions <= 1.30.1. It is fixed in 1.30.2. Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside.

How severe is CVE-2026-55187?

CVE-2026-55187 has a CVSS score of 5.8 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.

Which versions of github.com/axllent/mailpit are affected by CVE-2026-55187?

github.com/axllent/mailpit (go) versions <= 1.30.1 is affected.

Is there a fix for CVE-2026-55187?

Yes. CVE-2026-55187 is fixed in 1.30.2. Upgrade to this version or later.

Is CVE-2026-55187 exploitable, and should I be worried?

Whether CVE-2026-55187 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo

What actually determines whether CVE-2026-55187 is exploitable, and how bad it is?

Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.

How do I fix CVE-2026-55187?

Upgrade github.com/axllent/mailpit to 1.30.2 or later.

Stop the waste.
Protect your environment with Kodem.