From 826f59e89f199eb08aa4f1377dee7491ac7f7f35 Mon Sep 17 00:00:00 2001 From: liwang2019 Date: Tue, 25 Aug 2026 13:44:39 +0200 Subject: [PATCH 1/6] change: sanitize markdown-rendered HTML against stored XSS. --- v2/package.json | 1 + v2/util/fmtMd.js | 82 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/v2/package.json b/v2/package.json index 39f4b1e2..dea750ac 100644 --- a/v2/package.json +++ b/v2/package.json @@ -8,6 +8,7 @@ "cheerio": "^1.2.0", "marked": "18.0.7", "pug": "^3.0.4", + "sanitize-html": "2.16.0", "sharp": "^0.35.3", "swiper": "14.1.0" }, diff --git a/v2/util/fmtMd.js b/v2/util/fmtMd.js index 72e8c24b..f44dd26b 100755 --- a/v2/util/fmtMd.js +++ b/v2/util/fmtMd.js @@ -7,6 +7,7 @@ const fs = require('fs'); // marked is ESM-only; dynamic import() keeps this script working on // Node versions without require(esm) support. const {marked} = await import('marked'); +const {default: sanitizeHtml} = await import('sanitize-html'); const renderer = new marked.Renderer(); const defaultHtmlRenderer = renderer.html; const defaultLinkRenderer = renderer.link; @@ -46,6 +47,62 @@ class Slugger { const headingSlugger = new Slugger(); +// Neutralize dangerous URL schemes (javascript:, data:, vbscript:, file:) in +// Markdown links and images. marked does not block these itself, so a +// malicious `[x](javascript:alert(1))` would otherwise survive rendering. +function sanitizeUrl(url) { + if (url == null) { + return ''; + } + // Strip whitespace and control chars that could smuggle a scheme past a + // naive prefix check (e.g. "java\nscript:"). + const s = String(url).trim().replace(/[\u0000-\u001f\u007f]/g, ''); + if (/^(javascript|data|vbscript|file):/i.test(s)) { + return ''; + } + return s; +} + +// Whitelist for the rendered body HTML. Keeps the markup this site depends on +// (heading permalink anchors + their inline SVG, fenced code blocks with +// language classes, tables, GFM task lists, lazy-loaded images) while dropping +// everything else — notably raw \n\n\n\n\n\n\n" + i=i+1 out[i] = "\n\n\n\n\n\n\n\n\n\n\n" return out end @@ -644,7 +644,7 @@ Template['posts-slide-cn.tt2'] = function (stash) local i = 0 i=i+1 out[i] = "\n
\n

" i=i+1 out[i] = (Vmethod.l or noop)(get(stash, 'c'), "More Interesting Topics") - i=i+1 out[i] = "

\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n
\n
\n
\n
\n
\n\n \n \n\n\n\n\n" + i=i+1 out[i] = "\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n
\n
\n
\n
\n\n\n \n \n\n\n\n\n" return out end @@ -656,7 +656,7 @@ Template['posts-slide-en.tt2'] = function (stash) local i = 0 i=i+1 out[i] = "\n
\n

" i=i+1 out[i] = (Vmethod.l or noop)(get(stash, 'c'), "More Interesting Topics") - i=i+1 out[i] = "

\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n
\n
\n
\n
\n
\n\n \n \n\n\n\n\n" + i=i+1 out[i] = "\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n
\n
\n
\n
\n\n\n \n \n\n\n\n\n" return out end diff --git a/v2/templates/footer.tt2 b/v2/templates/footer.tt2 index a29e1434..4fae9721 100644 --- a/v2/templates/footer.tt2 +++ b/v2/templates/footer.tt2 @@ -11,10 +11,10 @@ - - - - + + + + + + \n\n" + i=i+1 out[i] = "\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n
\n
\n
\n
\n\n\n \n
\n
\n
\n \n
\n\n\n\n" return out end -MD5['posts-slide-cn.tt2'] = "af1d92201885a3cd78c001c7feb614c4" +MD5['posts-slide-cn.tt2'] = "59e560af183c39a258c5a9da38b53110" Template['posts-slide-en.tt2'] = function (stash) local out = {} local i = 0 i=i+1 out[i] = "\n
\n

" i=i+1 out[i] = (Vmethod.l or noop)(get(stash, 'c'), "More Interesting Topics") - i=i+1 out[i] = "

\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n
\n
\n
\n
\n
\n\n \n \n\n\n\n\n" + i=i+1 out[i] = "\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n
\n
\n
\n
\n\n\n \n
\n
\n
\n \n
\n\n\n\n" return out end -MD5['posts-slide-en.tt2'] = "cad4e3c736c7c75db59b0c2b2ac3c5fe" +MD5['posts-slide-en.tt2'] = "8a93c29fcf78baa74b32bc2ae57f0b1b" Template['search-result.tt2'] = function (stash) local out = {} diff --git a/v2/templates/posts-slide-cn.tt2 b/v2/templates/posts-slide-cn.tt2 index a2f520fb..8b8b45af 100644 --- a/v2/templates/posts-slide-cn.tt2 +++ b/v2/templates/posts-slide-cn.tt2 @@ -120,6 +120,9 @@ +
+
+
diff --git a/v2/templates/posts-slide-en.tt2 b/v2/templates/posts-slide-en.tt2 index 32785563..4542e692 100644 --- a/v2/templates/posts-slide-en.tt2 +++ b/v2/templates/posts-slide-en.tt2 @@ -120,6 +120,9 @@ +
+
+
diff --git a/v2/util/posts-slide.pug b/v2/util/posts-slide.pug index df643fd0..2f35ad54 100644 --- a/v2/util/posts-slide.pug +++ b/v2/util/posts-slide.pug @@ -21,6 +21,8 @@ dialog#blog-modal.blog-modal button.blog-modal-close(type="button" aria-label="Close") × + #blog-modal-loading.blog-modal-loading(role="status" aria-live="polite") + .blog-modal-spinner iframe#blog-iframe.blog-iframe(title="OpenResty Blog article") link(rel="stylesheet" href=swiperCss)