diff --git a/package-lock.json b/package-lock.json
index 4355e1640..b5211a3c2 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"
},
diff --git a/src/core/render/compiler/image.js b/src/core/render/compiler/image.js
index d113516b6..68bb478b4 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 44d2bcc2e..6c017adcf 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 3fa3cd799..a35e40e01 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 4d5d59026..fd01183ec 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=">