Skip to content

fix(editor): harden inline markdown renderer against XSS#55

Merged
benjamincanac merged 1 commit into
mainfrom
fix/editor-inline-markdown-xss
May 20, 2026
Merged

fix(editor): harden inline markdown renderer against XSS#55
benjamincanac merged 1 commit into
mainfrom
fix/editor-inline-markdown-xss

Conversation

@benjamincanac
Copy link
Copy Markdown
Contributor

@benjamincanac benjamincanac commented May 20, 2026

Summary

  • parseInline was interpolating user text directly into HTML, so markdown like <img onerror=alert(1)> or [x](javascript:alert(1)) reached innerHTML as live markup.
  • The DetailsBlock NodeView also fell back to dom.innerHTML = rawHtml when DOMParser couldn't find a <details> element — same XSS vector.

Changes

  • HTML-escape inline text before applying markdown patterns (links, code, bold, italic).
  • New isSafeUrl allowlists http(s) and mailto schemes for link href.
  • isSafeUrl also rejects any URL containing C0 control chars or DEL. Browsers strip tab/newline/CR from href values, so a naive scheme check on java<TAB>script:alert(1) would mis-classify it as a relative URL; once rendered the browser normalizes it back to javascript: and clicking executes. The control-char filter closes that bypass.
  • Replace the innerHTML = rawHtml fallback in the DetailsBlock NodeView with a plain [Invalid details block] text marker.

Notes for reviewer

  • parseInline / renderBasicMarkdown are only used inside an atom: true NodeView with contentEditable = 'false' (the <details> preview at editor.ts:202-253). The source markdown the user actually edits is unaffected.
  • The regex uses \x00-\x1F\x7F with an eslint-disable-next-line no-control-regex so the source stays readable.
  • Considered swapping to markdown-it (escapes by default, has a validateLink hook) — left as a follow-up so this PR stays scoped to the security fix.

Test plan

  • pnpm exec vue-tsc --noEmit passes.
  • Manual table of URL cases verified: javascript:, java<TAB>script:, java<LF>script:, data:, plain https://…, mailto:, relative paths, and fragments all classify correctly.
  • Verify in the editor that <details> previews still render normally and that malformed <details> blocks render the [Invalid details block] placeholder.
  • Verify a markdown link with an http(s) URL still renders as a clickable anchor in the details preview, and a javascript: link renders as escaped text.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced markdown link parsing with stricter URL validation and scheme checking, ensuring only safe links are rendered while preserving original text when validation fails
    • Added target="_blank" and rel="noopener" attributes to all rendered links
    • Improved error handling for malformed content blocks by displaying a clear placeholder instead of rendering raw HTML

Review Change Stack

`parseInline` previously interpolated user-supplied text directly into
HTML, so a markdown source like `<img onerror=alert(1)>` or
`[x](javascript:alert(1))` would reach `innerHTML` as live markup. The
`<details>` NodeView also fell back to assigning the raw decoded HTML to
`innerHTML` when DOMParser couldn't find a `<details>` element.

This change:
- Escapes HTML before applying markdown patterns.
- Allowlists `http(s)` and `mailto` schemes for link URLs, and rejects
  any URL containing C0 control characters or DEL — browsers strip
  tab/newline/CR from `href` values, which would otherwise let
  `java<TAB>script:` normalize back to `javascript:` after a naive
  scheme check.
- Replaces the `dom.innerHTML = rawHtml` fallback in the DetailsBlock
  NodeView with a plain text "[Invalid details block]" message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 96dcad8b-d15f-4c83-a9c5-c7a63630d682

📥 Commits

Reviewing files that changed from the base of the PR and between 3f80cf2 and b176286.

📒 Files selected for processing (1)
  • app/utils/editor.ts

📝 Walkthrough

Walkthrough

Markdown link parsing and HTML output handling are hardened to prevent XSS and injection attacks. The parseInline function now escapes HTML, decodes entities, validates URLs against a safe-scheme allowlist, and renders links only for safe URLs. Additionally, the DetailsBlock renderer displays a plain-text placeholder instead of raw HTML when the details element is absent.

Changes

Editor Security Hardening

Layer / File(s) Summary
Safe markdown link parser with URL validation
app/utils/editor.ts
Adds escapeHtml(), decodeHtmlEntities(), and isSafeUrl() helpers. Updates parseInline to escape surrounding text, decode link URLs, validate URLs against http(s) and mailto schemes only, and include target="_blank" and rel="noopener" on rendered links. Rejects unsafe URLs and leaves original markdown unchanged.
Invalid details block placeholder
app/utils/editor.ts
DetailsBlock renderer fallback now sets dom.textContent to "[Invalid details block]" instead of using dom.innerHTML with raw decoded HTML, preventing injection when details element is missing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

🐰 A rabbit hops through editor land so fine,
Links now safe from XSS designs,
HTML escapes and URLs align,
Details block glows with plain text shine. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main security-focused change: hardening the inline markdown renderer against XSS vulnerabilities, which aligns with the primary objectives of escaping HTML, validating URLs, and preventing unsafe HTML injection.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/editor-inline-markdown-xss

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@benjamincanac benjamincanac merged commit 18792bf into main May 20, 2026
4 checks passed
@benjamincanac benjamincanac deleted the fix/editor-inline-markdown-xss branch May 20, 2026 09:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant