GHSA-cv23-7vc5-jfh7: XSS
Summary
IsSafeURL slices url[:nPath] before checking that len(url) >= nPath, so a URL shorter than a path prefix (0, 1 or 2 bytes) triggers a slice-bounds panic, and this is reached end to end when the html.Safelink hardening flag is enabled and the input contains an empty or very short link destination, giving an unrecoverable crash DoS. Confirmed at HEAD (e08cff8; present since 2022).
The defect
parser/parser.go, IsSafeURL:
go func IsSafeURL(url []byte) bool { nLink := len(url) for , path := range validPaths { // "/", "./", "../" nPath := len(path) linkPrefix := url[:nPath] // line 789: sliced BEFORE the length guard if nLink >= nPath && bytes.Equal(linkPrefix, path) { // line 790: guard is too late ...
url[:nPath] is evaluated before the nLink >= nPath check. When len(url) < nPath and cap(url) < nPath, the slice expression panics. Only the relative-path loop is affected; the adjacent URIs loop is already correctly guarded (the signal that this one deviates, the same signal as the anchor). Input: IsSafeURL("") panics ([:1] cap 0), IsSafeURL(".") panics ([:2] cap 1), IsSafeURL("..") panics ([:3] cap 2), while IsSafeURL("/") returns true. End to end: markdown.ToHTML(input, html.CommonFlags|html.Safelink) with input x panics. Attacker model: (1) any service rendering untrusted Markdown with the html.Safelink flag (the URL-sanitizing hardening flag itself introduces the crash); or (2) any downstream that calls the exported parser.IsSafeURL on a 0/1/2-byte attacker URL, no flag required. No recover() anywhere, so an uncaught crash of the calling goroutine.
Verification
EXECUTED with negative controls. Direct: the empty/1/2-char cases panic with the exact slice-bounds message while "/" returns true. End to end via ToHTML with html.CommonFlags|html.Safelink: x panics, while x, x, plain text, and x) (safely dropped) do not. NOT verified: how many downstream apps enable Safelink or call IsSafeURL directly (the default no-Safelink render path does not reach it).
Suggested fix
Move the slice inside the guard: check nLink >= nPath first, then compute url[:nPath] (or use bytes.HasPrefix(url, path), which is length-safe). One-line change, mirroring the already-correct URIs loop.
Also checked and sound: CVE-2026-40890 (smartypants smartLeftAngle OOB) is fixed; CVE-2024-44337 (list/paragraph infinite loop) is fixed (deep nesting capped at maxNesting=64); default XSS is by design (Safelink/SkipHTML not in CommonFlags).
Tooling
AI assistance was used while investigating. The panic is executed-verified with negative controls (IsSafeURL on empty/./.. panics with a slice-bounds error while "/" returns true; ToHTML with html.Safelink on x panics while valid and javascript: links do not), and I confirmed at HEAD that IsSafeURL computes url[:nPath] before the nLink >= nPath guard.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/github.com/gomarkdown/markdownto a version that resolves this vulnerability.Fixed in 0.0.0-20260724235538-61af6e1e6971 - Compensating control
In parser/parser.go, update IsSafeURL so it checks nLink >= nPath before evaluating url[:nPath]; alternatively use bytes.HasPrefix(url, path), which is length-safe.
Event History
Frequently Asked Questions
Which deployments are exposed to the end-to-end crash?
Applications using go/github.com/gomarkdown/markdown are exposed when the html.Safelink hardening flag is enabled and they process input containing an empty or very short link destination. The available data does not state whether this flag is enabled by default.
What input is needed to trigger the panic?
A link destination shorter than one of the relative-path prefixes can trigger the vulnerable slice operation. Confirmed examples are an empty destination, ".", and ".."; "/" does not panic.
Does exploitation require authentication or user interaction?
The supplied vector indicates network reachability, low attack complexity, no privileges required, and no user interaction. In an application that accepts attacker-controlled Markdown or link destinations under the affected configuration, processing the input can cause an unrecoverable denial-of-service crash.
How can I determine whether the vulnerable code is present?
The issue was confirmed at repository HEAD commit e08cff8 and is reported as present since 2022. The affected implementation is parser/parser.go's IsSafeURL function, where url[:nPath] is evaluated before verifying that len(url) is at least nPath.
Is there a referenced code fix?
The advisory references commit 61af6e1e69710c5ca13cfbaaaa1bc11bc763a8a9. The provided data does not identify a released fixed version.