diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js index 0fac23bee..2d6f813c2 100644 --- a/src/core/fetch/index.js +++ b/src/core/fetch/index.js @@ -22,7 +22,7 @@ function loadNested(path, qs, file, next, vm, first) { function isExternal(url) { let match = url.match( - /^([^:/?#]+:)?(?:\/\/([^/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/ + /^([^:/?#]+:)?(?:\/{2,}([^/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/ ); if ( typeof match[1] === 'string' && diff --git a/test/e2e/security.test.js b/test/e2e/security.test.js new file mode 100644 index 000000000..caffddad6 --- /dev/null +++ b/test/e2e/security.test.js @@ -0,0 +1,32 @@ +const docsifyInit = require('../helpers/docsify-init'); + +describe(`Security`, function() { + const sharedOptions = { + markdown: { + homepage: '# Hello World', + }, + routes: { + 'test.md': '# Test Page', + }, + }; + + describe(`Cross Site Scripting (XSS)`, function() { + const slashStrings = ['//', '///']; + + for (const slashString of slashStrings) { + const hash = `#${slashString}domain.com/file.md`; + + test(`should not load remote content from hash (${hash})`, async () => { + await docsifyInit(sharedOptions); + await expect(page).toHaveText('#main', 'Hello World'); + await page.evaluate(() => (location.hash = '#/test')); + await expect(page).toHaveText('#main', 'Test Page'); + await page.evaluate(newHash => { + location.hash = newHash; + }, hash); + await expect(page).toHaveText('#main', 'Hello World'); + expect(page.url()).toMatch(/#\/$/); + }); + } + }); +});