GHSA-CV23-7VC5-JFH7
Vulnerability from github – Published: 2026-10-16 15:41 – Updated: 2026-10-16 15:41Summary
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:
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](http://example.com), plain text, and [x](javascript:alert(1)) (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": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/gomarkdown/markdown"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.0.0-20260724235538-61af6e1e6971"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-102941"
],
"database_specific": {
"cwe_ids": [
"CWE-125"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-16T15:41:39Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\nIsSafeURL slices url[:nPath] before checking that len(url) \u003e= 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).\n\n## The defect\n\nparser/parser.go, IsSafeURL:\n\n```go\nfunc IsSafeURL(url []byte) bool {\n nLink := len(url)\n for _, path := range validPaths { // \"/\", \"./\", \"../\"\n nPath := len(path)\n linkPrefix := url[:nPath] // line 789: sliced BEFORE the length guard\n if nLink \u003e= nPath \u0026\u0026 bytes.Equal(linkPrefix, path) { // line 790: guard is too late\n ...\n```\n\nurl[:nPath] is evaluated before the `nLink \u003e= nPath` check. When len(url) \u003c nPath and cap(url) \u003c 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.\n\n## Verification\n\nEXECUTED 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](http://example.com)`, plain text, and `[x](javascript:alert(1))` (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).\n\n## Suggested fix\n\nMove the slice inside the guard: check `nLink \u003e= 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.\n\nAlso 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).\n\n## Tooling\n\nAI 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 \u003e= nPath` guard.",
"id": "GHSA-cv23-7vc5-jfh7",
"modified": "2026-10-16T15:41:39Z",
"published": "2026-10-16T15:41:39Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/gomarkdown/markdown/security/advisories/GHSA-cv23-7vc5-jfh7"
},
{
"type": "WEB",
"url": "https://github.com/gomarkdown/markdown/commit/61af6e1e69710c5ca13cfbaaaa1bc11bc763a8a9"
},
{
"type": "PACKAGE",
"url": "https://github.com/gomarkdown/markdown"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "gomarkdown/markdown IsSafeURL panics on short URLs"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.