Current state
io/notion/renderBlocksToHtml.ts line 71 interpolates rich text link URLs from Notion directly into href attributes in the RSS feed HTML:
if (item.link) text = `<a href="${item.link}">${text}</a>`
The link field is validated by z.url(), but Zod's z.url() delegates to the JavaScript URL constructor, which accepts javascript: as a valid scheme. A Notion-authored link with a javascript: URL passes validation and is rendered verbatim in the RSS output. RSS reader applications that render HTML (Feedly, NetNewsWire, etc.) may execute the script when the link is clicked.
The same unescaped URL interpolation applies to image and video block URLs on lines 54–56, which are also interpolated into src and href attributes without HTML-encoding — a URL containing " can break out of the attribute context.
Ideal state
- Before interpolating any URL into an
href or src attribute, the renderer checks that the scheme is http: or https:.
- A
javascript:, data:, or any other non-HTTP/HTTPS URL from Notion produces no <a> or <img> tag in the RSS output.
- All URL values are also HTML-encoded (replacing
" with ") before attribute interpolation.
Starting points
io/notion/renderBlocksToHtml.ts line 71 — the href="${item.link}" interpolation in the rich text renderer
io/notion/renderBlocksToHtml.ts lines 54–56 — src="${block.url}" and href="${block.url}" for image and video blocks
QA plan
- Author a Notion page with a rich text link whose URL is
javascript:alert(1). Fetch the RSS route. Inspect the generated HTML — confirm no <a href="javascript:..."> appears in the output.
- Author a link with
data:text/html,<script>alert(1)</script>. Confirm it is also blocked.
- Author a link with a URL containing a double-quote (e.g.
https://example.com/?a="b"). Inspect the rendered HTML — confirm the quote is HTML-encoded and does not break the attribute.
- Author a valid
https://example.com link. Confirm it renders correctly as <a href="https://example.com">.
Done when
No javascript:, data:, or non-HTTP/HTTPS URL from Notion can appear as an href or src attribute value in the RSS feed output, and all URLs are HTML-encoded before attribute interpolation.
Current state
io/notion/renderBlocksToHtml.tsline 71 interpolates rich text link URLs from Notion directly intohrefattributes in the RSS feed HTML:The
linkfield is validated byz.url(), but Zod'sz.url()delegates to the JavaScriptURLconstructor, which acceptsjavascript:as a valid scheme. A Notion-authored link with ajavascript:URL passes validation and is rendered verbatim in the RSS output. RSS reader applications that render HTML (Feedly, NetNewsWire, etc.) may execute the script when the link is clicked.The same unescaped URL interpolation applies to image and video block URLs on lines 54–56, which are also interpolated into
srcandhrefattributes without HTML-encoding — a URL containing"can break out of the attribute context.Ideal state
hreforsrcattribute, the renderer checks that the scheme ishttp:orhttps:.javascript:,data:, or any other non-HTTP/HTTPS URL from Notion produces no<a>or<img>tag in the RSS output."with") before attribute interpolation.Starting points
io/notion/renderBlocksToHtml.tsline 71 — thehref="${item.link}"interpolation in the rich text rendererio/notion/renderBlocksToHtml.tslines 54–56 —src="${block.url}"andhref="${block.url}"for image and video blocksQA plan
javascript:alert(1). Fetch the RSS route. Inspect the generated HTML — confirm no<a href="javascript:...">appears in the output.data:text/html,<script>alert(1)</script>. Confirm it is also blocked.https://example.com/?a="b"). Inspect the rendered HTML — confirm the quote is HTML-encoded and does not break the attribute.https://example.comlink. Confirm it renders correctly as<a href="https://example.com">.Done when
No
javascript:,data:, or non-HTTP/HTTPS URL from Notion can appear as anhreforsrcattribute value in the RSS feed output, and all URLs are HTML-encoded before attribute interpolation.