From b2f3c69c3d73adc21d6a380a311f6123cb31c6b6 Mon Sep 17 00:00:00 2001 From: Sebastion Date: Sun, 5 Apr 2026 14:17:45 +0100 Subject: [PATCH] fix: escape HTML attribute values in markdown htmlTag to prevent stored XSS The htmlTag function interpolated attribute values directly into the HTML string without escaping. A crafted emoji name like :a"onclick="alert(1): could break out of the attribute context and inject arbitrary HTML attributes or tags. Since the rendered HTML is passed to Vue's runtime template compiler via h({ template: ... }), this also enables Vue directive injection (e.g. v-on:click). Add an escapeAttr helper that escapes &, ", <, >, and ' characters in attribute values before interpolation. --- .../client/src/components/markdown.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/images/chromium-headful/client/src/components/markdown.ts b/images/chromium-headful/client/src/components/markdown.ts index 38c6c357..7469ea46 100644 --- a/images/chromium-headful/client/src/components/markdown.ts +++ b/images/chromium-headful/client/src/components/markdown.ts @@ -37,6 +37,15 @@ interface HTMLAttributes { interface MarkdownState extends State {} +function escapeAttr(value: string): string { + return value + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>') + .replace(/'/g, ''') +} + function htmlTag( tagName: string, content: string, @@ -58,7 +67,7 @@ function htmlTag( let attributeString = '' for (const attr in attributes) { if (Object.prototype.hasOwnProperty.call(attributes, attr) && attributes[attr]) { - attributeString += ` ${attr}="${attributes[attr]}"` // md.sanitizeText(attr) + attributeString += ` ${attr}="${escapeAttr(attributes[attr])}"` } }