#62 Markdown link/image URLs are not scheme-filtered (javascript: XSS)
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
Uunknown ·
Fixed in 2b56eb82: GFM options + .markdown wrapper (#71); scheme allowlist with entity decoding before the check (#62).