id: 7b0b0eae-93d2-4cb3-8fa5-e01cbf1447ac title: 'Markdown link/image URLs are not scheme-filtered (javascript: XSS)' state: closed labels:
- security assignees: [] milestone: null created: 2026-10-27T14:53:39.317738326Z updated: 2026-10-27T16:04:34.720653453Z
Markdown link and image destinations are emitted verbatim, so [click](javascript:...) renders as a live javascript: link on the mirror.
Verified with a committed README:
[click me](javascript:alert(document.domain))
)
$ grep -o '<a href="[^"]*"' out/index.html
<a href="javascript:alert(document.domain)"
$ grep -o '<img src="[^"]*"' out/index.html
<img src="javascript:alert(2)"
markdown_to_html intercepts Event::Html / Event::InlineHtml and escapes them (good — raw <script> in markdown is neutralized), but Tag::Link { dest_url } / Tag::Image { dest_url } pass through untouched. pulldown-cmark does not sanitize URLs by design.
Impact: stored XSS one click away from README, wiki pages, issue/PR bodies and release notes — same origin as /inbox/*, so any injected script can post submissions with the visitor's IP. Complements #61 (which needs no click) — fixing both closes the pair.
Fix: keep intercepting events in markdown_to_html and add a Tag arm that rewrites Link/Image destinations through an allowlist:
- allow
http:,https:,mailto:, and relative/fragment///hostforms - everything else (javascript:, data:, vbscript:, file:) renders with an empty/
#href or as plain text - note
escdoes not percent-encode, so decide explicitly whether to encode the URL (currently#/?/space in a filename break the link, a separate correctness nit)
Acceptance:
-
[x](javascript:alert(1))never reaches anhref/src -
data:andvbscript:likewise - legit links (relative files, https, mailto) unchanged
-
unit test on
markdown_to_htmlcovering each scheme