From b8f70321b98d1747a025b2ce9ec0987971bd9be3 Mon Sep 17 00:00:00 2001
From: Luffy
Date: Fri, 10 Apr 2026 11:27:43 +0800
Subject: [PATCH 1/2] fix: escape HTML in image, link, and media components
(#2718)
---
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=">
"
'"
"',
);
});
+
+ test('escapes image alt and title to prevent attribute injection', async function () {
+ const output = window.marked(
+ '\')',
+ );
+
+ expect(output).not.toContain(' onerror="alert(1)"');
+ expect(output).toContain('alt="alt" onerror="alert(1)"');
+ expect(output).toContain('title="title" onerror="alert(1)"');
+ });
});
// Headings
@@ -377,6 +387,15 @@ Text"
`"alt text
"`,
);
});
+
+ test('escapes link title to prevent attribute injection', async function () {
+ const output = window.marked(
+ `[alt text](http://url 'title" onclick="alert(1)')`,
+ );
+
+ expect(output).not.toContain(' onclick="alert(1)"');
+ expect(output).toContain('title="title" onclick="alert(1)"');
+ });
});
// Skip Link
From ab665986a60ee0b5a9138b9a2b0ecaea1814890b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 10 Apr 2026 11:32:50 +0800
Subject: [PATCH 2/2] chore: bump lodash from 4.17.23 to 4.18.1 (#2717)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)
---
updated-dependencies:
- dependency-name: lodash
dependency-version: 4.18.1
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
package-lock.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 4355e1640a..b5211a3c25 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14507,9 +14507,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
- "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"dev": true,
"license": "MIT"
},