From ad0b4aa8dde72702ecd1d74883f0388fb5eead88 Mon Sep 17 00:00:00 2001
From: sy-records <52o@qq52o.cn>
Date: Thu, 9 Apr 2026 12:10:57 +0800
Subject: [PATCH] fix: escape HTML in image, link, and media components
---
src/core/render/compiler/image.js | 6 +++---
src/core/render/compiler/link.js | 6 +++---
src/core/render/compiler/media.js | 8 +++++---
test/integration/example.test.js | 26 ++++++++++++++++++++++++--
test/integration/render.test.js | 19 +++++++++++++++++++
5 files changed, 54 insertions(+), 11 deletions(-)
diff --git a/src/core/render/compiler/image.js b/src/core/render/compiler/image.js
index d113516b61..68bb478b47 100644
--- a/src/core/render/compiler/image.js
+++ b/src/core/render/compiler/image.js
@@ -1,4 +1,4 @@
-import { getAndRemoveConfig } from '../utils.js';
+import { escapeHtml, getAndRemoveConfig } from '../utils.js';
import { isAbsolutePath, getPath, getParentPath } from '../../router/util.js';
export const imageCompiler = ({ renderer, contentBase, router }) =>
@@ -14,7 +14,7 @@ export const imageCompiler = ({ renderer, contentBase, router }) =>
}
if (title) {
- attrs.push(`title="${title}"`);
+ attrs.push(`title="${escapeHtml(title)}"`);
}
if (config.size) {
@@ -42,7 +42,7 @@ export const imageCompiler = ({ renderer, contentBase, router }) =>
url = getPath(contentBase, getParentPath(router.getCurrentPath()), href);
}
- return /* html */ ``;
});
diff --git a/src/core/render/compiler/link.js b/src/core/render/compiler/link.js
index 44d2bcc2e3..6c017adcf3 100644
--- a/src/core/render/compiler/link.js
+++ b/src/core/render/compiler/link.js
@@ -1,4 +1,4 @@
-import { getAndRemoveConfig } from '../utils.js';
+import { escapeHtml, getAndRemoveConfig } from '../utils.js';
import { isAbsolutePath } from '../../router/util.js';
export const linkCompiler = ({
@@ -65,8 +65,8 @@ export const linkCompiler = ({
}
if (title) {
- attrs.push(`title="${title}"`);
+ attrs.push(`title="${escapeHtml(title)}"`);
}
- return /* html */ `${text}`;
+ return /* html */ `${text}`;
});
diff --git a/src/core/render/compiler/media.js b/src/core/render/compiler/media.js
index 3fa3cd799b..a35e40e015 100644
--- a/src/core/render/compiler/media.js
+++ b/src/core/render/compiler/media.js
@@ -1,3 +1,5 @@
+import { escapeHtml } from '../utils';
+
export const compileMedia = {
markdown(url) {
return {
@@ -11,19 +13,19 @@ export const compileMedia = {
},
iframe(url, title) {
return {
- html: ``,
};
},
video(url, title) {
return {
- html: ``,
+ html: ``,
};
},
audio(url, title) {
return {
- html: ``,
+ html: ``,
};
},
code(url, title) {
diff --git a/test/integration/example.test.js b/test/integration/example.test.js
index 4d5d590269..fd01183ec6 100644
--- a/test/integration/example.test.js
+++ b/test/integration/example.test.js
@@ -228,9 +228,9 @@ describe('Creating a Docsify site (integration tests in Jest)', function () {
# Text between
[filename](_media/example3.js ':include :fragment=something_else_not_code')
-
+
[filename](_media/example4.js ':include :fragment=demo')
-
+
# Text after
`,
},
@@ -303,4 +303,26 @@ Command | Description | Parameters
expect(mainText).toContain('Something');
expect(mainText).toContain('this is include content');
});
+
+ test.each([
+ { type: 'iframe', selector: 'iframe' },
+ { type: 'video', selector: 'video' },
+ { type: 'audio', selector: 'audio' },
+ ])('embed %s escapes URL for XSS safety', async ({ type, selector }) => {
+ const dangerousUrl = 'https://example.com/?q=">