diff --git a/.changeset/rich-plants-call.md b/.changeset/rich-plants-call.md new file mode 100644 index 00000000..ddd30f55 --- /dev/null +++ b/.changeset/rich-plants-call.md @@ -0,0 +1,5 @@ +--- +"domiso": minor +--- + +feat: better compatibility with `dompurify`, prevent many more XSS cases diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index f39103e6..72f28cd3 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -1,4 +1,6 @@ { "node": "18", + "installCommand": "codesandbox:install", + "buildCommand": "codesandbox:build", "sandboxes": [] } diff --git a/.eslintignore b/.eslintignore index f9f44702..3f17f713 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,6 +2,7 @@ coverage dist lib CHANGELOG.md +/DOMPurify /auto-imports.d.ts /pnpm-lock.yaml !/.github diff --git a/.eslintrc b/.eslintrc index a31405f5..ad718483 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,6 +2,7 @@ "root": true, "extends": "@1stg", "rules": { + "unicorn/prefer-set-has": "off", "unicorn/template-indent": "off" }, "overrides": [ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ef17711..5634d791 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,8 @@ jobs: steps: - name: Checkout Repo uses: actions/checkout@v4 + with: + submodules: true - name: Setup pnpm uses: pnpm/action-setup@v2 diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml index 311f7c21..b4bc732e 100644 --- a/.github/workflows/size-limit.yml +++ b/.github/workflows/size-limit.yml @@ -12,6 +12,8 @@ jobs: CI_JOB_NUMBER: 1 steps: - uses: actions/checkout@v4 + with: + submodules: true - name: Setup pnpm uses: pnpm/action-setup@v2 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..69d61b38 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "DOMPurify"] + path = DOMPurify + url = https://github.com/cure53/DOMPurify.git diff --git a/.npmrc b/.npmrc index f05793b2..9f1ae627 100644 --- a/.npmrc +++ b/.npmrc @@ -1,4 +1,3 @@ -auto-install-peers=true enable-pre-post-scripts=true public-hoist-pattern[]=@1stg/* public-hoist-pattern[]=@commitlint/* diff --git a/.prettierignore b/.prettierignore index aaa2ad02..8df66a17 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,4 +3,6 @@ coverage dist lib +/DOMPurify +/auto-imports.d.ts /pnpm-lock.yaml diff --git a/.stylelintignore b/.stylelintignore index c19c6f24..265859dd 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -2,8 +2,11 @@ coverage dist lib LICENSE +fixtures +/DOMPurify *.json *.log +*.mts *.patch *.snap *.svg diff --git a/DOMPurify b/DOMPurify new file mode 160000 index 00000000..d1e4f216 --- /dev/null +++ b/DOMPurify @@ -0,0 +1 @@ +Subproject commit d1e4f216664cc9e1ba5498a00b7d88a9cbd4c7f0 diff --git a/dompurify.fixtures.d.mts b/dompurify.fixtures.d.mts new file mode 100644 index 00000000..5c1ab3e0 --- /dev/null +++ b/dompurify.fixtures.d.mts @@ -0,0 +1,11 @@ +declare module 'DOMPurify/test/fixtures/expect.mjs' { + export interface Fixture { + title?: string + payload: string + expected: string[] | string + } + + const fixtures: Fixture[] + + export default fixtures +} diff --git a/package.json b/package.json index d48563a4..38d1c787 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "author": "JounQin (https://www.1stG.me) ", "funding": "https://opencollective.com/unts", "license": "MIT", - "packageManager": "pnpm@8.12.0", + "packageManager": "pnpm@8.12.1", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -34,14 +34,17 @@ "build": "pnpm test && run-p build:*", "build:r": "r -f cjs", "build:tsc": "tsc -p src", + "codesandbox:build": "yarn test -u && yarn run-p build:*", + "codesandbox:install": "git submodule update --init && yarn", "dev": "vitest", "docs:build": "w -e docs -p --publicPath /", "docs:dev": "w -e docs", + "limit": "pnpm build && size-limit", "lint": "run-p lint:*", "lint:es": "eslint . --cache -f friendly --max-warnings 10", "lint:style": "stylelint . --cache", "lint:tsc": "tsc --noEmit", - "prepare": "simple-git-hooks", + "prepare": "simple-git-hooks || exit 0", "release": "pnpm build && changeset publish", "serve": "sirv dist -s", "test": "vitest run --coverage", @@ -63,7 +66,7 @@ "@types/react-dom": "^18.2.17", "@types/web": "^0.0.127", "@vitest/coverage-istanbul": "^1.0.4", - "domiso": "link:", + "domiso": "link:.", "github-markdown-css": "^5.5.0", "jsdom": "^23.0.1", "react": "^18.2.0", @@ -89,12 +92,15 @@ "pnpm": { "overrides": { "sh-syntax": "^0.4.1" + }, + "patchedDependencies": { + "jsdom@23.0.1": "patches/jsdom@23.0.1.patch" } }, "size-limit": [ { "path": "lib/index.js", - "limit": "410B" + "limit": "1KB" } ], "typeCoverage": { diff --git a/patches/jsdom@23.0.1.patch b/patches/jsdom@23.0.1.patch new file mode 100644 index 00000000..e8ea06ec --- /dev/null +++ b/patches/jsdom@23.0.1.patch @@ -0,0 +1,29 @@ +diff --git a/lib/jsdom/living/helpers/stylesheets.js b/lib/jsdom/living/helpers/stylesheets.js +index 9b44b6dba9a74495f880e6a6fe53ffbc9bf8a5d6..cb536fc5b11ca1eb0c5f98e9292ce46f63c03290 100644 +--- a/lib/jsdom/living/helpers/stylesheets.js ++++ b/lib/jsdom/living/helpers/stylesheets.js +@@ -43,6 +43,11 @@ exports.createStylesheet = (sheetText, elementImpl, baseURL) => { + return; + } + ++ if (!elementImpl._ownerDocument._defaultView) { ++ elementImpl.sheet = sheet; ++ return; ++ } ++ + scanForImportRules(elementImpl, sheet.cssRules, baseURL); + + addStylesheet(sheet, elementImpl); +diff --git a/lib/jsdom/living/nodes/HTMLStyleElement-impl.js b/lib/jsdom/living/nodes/HTMLStyleElement-impl.js +index 7dcc6fbd453ec5edda80f07c0e79a2fe6e6c9906..2116266218ca6420457518595cdd4f3f73eea966 100644 +--- a/lib/jsdom/living/nodes/HTMLStyleElement-impl.js ++++ b/lib/jsdom/living/nodes/HTMLStyleElement-impl.js +@@ -52,7 +52,7 @@ class HTMLStyleElementImpl extends HTMLElementImpl { + } + + // Browsing-context connected, per https://github.com/whatwg/html/issues/4547 +- if (!this.isConnected || !this._ownerDocument._defaultView) { ++ if (!this.isConnected) { + return; + } + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4d99cbf..64872885 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,11 @@ settings: overrides: sh-syntax: ^0.4.1 +patchedDependencies: + jsdom@23.0.1: + hash: c4vz5hwxbqpkrhc7f5hqkxfedi + path: patches/jsdom@23.0.1.patch + devDependencies: '@1stg/app-config': specifier: ^9.0.1 @@ -48,14 +53,14 @@ devDependencies: specifier: ^1.0.4 version: 1.0.4(vitest@1.0.4) domiso: - specifier: 'link:' + specifier: link:. version: 'link:' github-markdown-css: specifier: ^5.5.0 version: 5.5.0 jsdom: specifier: ^23.0.1 - version: 23.0.1 + version: 23.0.1(patch_hash=c4vz5hwxbqpkrhc7f5hqkxfedi) react: specifier: ^18.2.0 version: 18.2.0 @@ -10469,6 +10474,7 @@ packages: /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + requiresBuild: true dependencies: safer-buffer: 2.1.2 dev: true @@ -11322,7 +11328,7 @@ packages: hasBin: true dev: true - /jsdom@23.0.1: + /jsdom@23.0.1(patch_hash=c4vz5hwxbqpkrhc7f5hqkxfedi): resolution: {integrity: sha512-2i27vgvlUsGEBO9+/kJQRbtqtm+191b5zAZrU/UezVmnC2dlDAFLgDYJvAEi94T4kjsRKkezEtLQTgsNEsW2lQ==} engines: {node: '>=18'} peerDependencies: @@ -11357,6 +11363,7 @@ packages: - supports-color - utf-8-validate dev: true + patched: true /jsesc@0.5.0: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} @@ -19513,7 +19520,7 @@ packages: chai: 4.3.10 debug: 4.3.4 execa: 8.0.1 - jsdom: 23.0.1 + jsdom: 23.0.1(patch_hash=c4vz5hwxbqpkrhc7f5hqkxfedi) local-pkg: 0.5.0 magic-string: 0.30.5 pathe: 1.1.1 diff --git a/src/index.ts b/src/index.ts index 85f863d0..72a3f541 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,49 @@ let domParser: DOMParser | undefined -const isDocument = (el: Document | Element): el is Document => - el.nodeType === el.DOCUMENT_NODE +export type DocumentOrFragment = Document | DocumentFragment + +const isDocumentOrFragment = ( + el: ChildNode | DocumentOrFragment, +): el is DocumentOrFragment => + el.nodeType === el.DOCUMENT_NODE || el.nodeType === el.DOCUMENT_FRAGMENT_NODE + +const isElement = (el: ChildNode | DocumentOrFragment): el is Element => + el.nodeType === el.ELEMENT_NODE + +const isComment = (el: ChildNode): el is Comment => + el.nodeType === el.COMMENT_NODE + +function getTagName(el: Element): string +function getTagName(el: ChildNode | DocumentOrFragment): string | undefined +function getTagName(el: ChildNode | DocumentOrFragment) { + return isElement(el) ? el.tagName.toLowerCase() : undefined +} + +/** + * @see https://www.w3schools.com/tags/att_form.asp + */ +const DISALLOWED_FORM_ATTR_TAG_NAMES = + 'button,fieldset,input,label,meter,object,output,select,textarea'.split(',') + +const DISALLOWED_ATTR_NAMES = [ + 'autofocus', + ...'fld,formatas,src'.split(',').map(it => `data${it}`), +] const sanitizeAttributes = (el: Element) => { + const tagName = getTagName(el) const attrs = el.attributes for (let i = 0, len = attrs.length; i < len; i++) { const attr = attrs[i] - if ( - /^on/i.test(attr.name) || - /^(?:data|javascript|vbscript):/i.test(attr.value) + const { name, value } = attr + if (name === 'is') { + attr.value = '' + } else if ( + DISALLOWED_ATTR_NAMES.includes(name) || + (name === 'form' && DISALLOWED_FORM_ATTR_TAG_NAMES.includes(tagName)) || + /^(?:["'<=>`]|on)/i.test(name) || + /\s/.test(name) || + /^(?:\w+script|data):/i.test(value.replaceAll(/\r?\n/g, '')) ) { el.removeAttributeNode(attr) // eslint-disable-next-line sonar/updated-loop-counter -- the attribute is removed, the index and length must be rechecked @@ -20,11 +54,16 @@ const sanitizeAttributes = (el: Element) => { return el } -const sanitizeChildren = (el: T) => { - for (let i = 0, len = el.children.length; i < len; i++) { - const sanitized = sanitizeNode(el.children[i]) - if (sanitized == null) { - // eslint-disable-next-line sonar/updated-loop-counter -- the child is removed, the index and length must be rechecked +const sanitizeChildren = (el: T) => { + for (let i = 0, len = el.childNodes.length; i < len; i++) { + const item = el.childNodes[i] + const sanitized = sanitizeNode(item, getTagName(el)) + if (sanitized === item) { + continue + } + if (sanitized == null || typeof sanitized === 'string') { + item.replaceWith(...(sanitized == null ? [] : [sanitized])) + // eslint-disable-next-line sonar/updated-loop-counter -- the child is removed or replaced by text, the index and length must be rechecked i-- len-- } @@ -32,16 +71,76 @@ const sanitizeChildren = (el: T) => { return el } +/** + * @see https://developer.mozilla.org/en-US/docs/Web/MathML/Authoring#using_mathml + */ +const MathML_TAG_NAMES = + 'error,frac,i,multiscripts,n,o,over,padded,phantom,root,row,s,space,sqrt,style,sub,subsup,sup,table,td,text,tr,under,underover' + .split(',') + .map(it => `m${it}`) + +const DANGEROUS_OR_OBSOLETE_TAG_NAMES = 'event-source,listing'.split(',') + function sanitizeNode(el: Document): Document -function sanitizeNode(el: Element): Element | null -function sanitizeNode(el: Document | Element) { - if (isDocument(el)) { +function sanitizeNode(el: DocumentFragment): DocumentFragment +function sanitizeNode( + el: ChildNode, + parentTagName?: string, +): ChildNode | string | null | undefined +function sanitizeNode( + el: ChildNode | DocumentOrFragment, + parentTagName?: string, +) { + if (isDocumentOrFragment(el)) { return sanitizeChildren(el) } - if (['parsererror', 'script'].includes(el.tagName.toLowerCase())) { - el.remove() - return null + if (isComment(el)) { + return + } + + if (!isElement(el)) { + return el + } + + const tagName = getTagName(el) + + if ( + (parentTagName === 'math' && !MathML_TAG_NAMES.includes(tagName)) || + DANGEROUS_OR_OBSOLETE_TAG_NAMES.includes(tagName) || + // unknown HTML element + el instanceof HTMLUnknownElement || + // unknown SVG element + (Object.getPrototypeOf(el) === SVGElement.prototype && + // https://github.com/jsdom/jsdom/issues/2734 + !['defs', 'filter', 'g', 'script'].includes(tagName)) + ) { + return el.textContent + } + + switch (tagName) { + case 'style': { + const { sheet } = el as HTMLStyleElement + if (sheet?.ownerRule || sheet?.cssRules.length) { + break + } + } + // eslint-disable-next-line no-fallthrough -- intended to remove empty style element + case 'embed': + case 'iframe': + case 'link': + case 'meta': + case 'object': + case 'parsererror': + case 'script': + // eslint-disable-next-line no-fallthrough -- deprecated tags + case 'noembed': + case 'xmp': { + return el.remove() + } + case 'template': { + sanitizeChildren((el as HTMLTemplateElement).content) + } } return sanitizeChildren(sanitizeAttributes(el)) @@ -50,23 +149,43 @@ function sanitizeNode(el: Document | Element) { export const TEXT_HTML = 'text/html' export const IMAGE_SVG_XML = 'image/svg+xml' +export interface SanitizeOptions { + type?: DOMParserSupportedType + fragment?: boolean +} + export const sanitize = ( domString: string, - type: DOMParserSupportedType = TEXT_HTML, + typeOrFragment?: DOMParserSupportedType | boolean, ) => { + const trimmed = domString.trim() + + if (!trimmed) { + return domString + } + if (!domParser) { domParser = new DOMParser() } - const doc = sanitizeNode(domParser.parseFromString(domString, type)) + const { type = TEXT_HTML, fragment }: SanitizeOptions = + typeOrFragment == null || typeof typeOrFragment === 'string' + ? { type: typeOrFragment } + : { fragment: typeOrFragment } + + const doc = sanitizeNode( + domParser.parseFromString( + // make sure the string is wrapped in a body tag + fragment ? `${domString}` : domString, + type, + ), + ) return ( - ((type !== IMAGE_SVG_XML || - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- `document.body` is unavailable in XML, see also https://github.com/microsoft/TypeScript/issues/29052#issuecomment-447998135 - !doc.body) && - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- https://github.com/microsoft/TypeScript/issues/50078 - doc.documentElement?.outerHTML) || - '' + (fragment && type === TEXT_HTML + ? doc.body.innerHTML + : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- https://github.com/microsoft/TypeScript/issues/50078 + doc.documentElement?.outerHTML) || '' ) } diff --git a/test/__snapshots__/dompurify.spec.ts.snap b/test/__snapshots__/dompurify.spec.ts.snap new file mode 100644 index 00000000..0968e703 --- /dev/null +++ b/test/__snapshots__/dompurify.spec.ts.snap @@ -0,0 +1,854 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`dompurify compatibility > >style< > >style< 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > 81 > 81 1`] = ` +{ + "expected": "
//["'\`-->]]>]
1>//["'\`-->]]>]
", + "payload": "
//["'\`-->]]>]
1>//["'\`-->]]>]
", + "result": "
//["'\`-->]]>]
1>//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > 99 > 99 1`] = ` +{ + "expected": "Hello +//["'\`-->]]>]", + "payload": " + + + +Hello +//["'\`-->]]>]", + "result": " + +Hello +//["'\`-->]]>]", +} +`; + +exports[`dompurify compatibility > 100 > 100 1`] = ` +{ + "expected": "
X//["'\`-->]]>]
", + "payload": "
X//["'\`-->]]>]
", + "result": "
X//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > 121 > 121 1`] = ` +{ + "expected": "//["'\`-->]]>] +
+]>//["'\`-->]]>]
", + "payload": " +//["'\`-->]]>] +
+]>//["'\`-->]]>]
", + "result": "//["'\`-->]]>] + +]>//["'\`-->]]>]", +} +`; + +exports[`dompurify compatibility > 139 > 139 1`] = ` +{ + "expected": "
//["'\`-->]]>]
+ +//["'\`-->]]>]
", + "payload": "
//["'\`-->]]>]
+ +//["'\`-->]]>]
", + "result": "
//["'\`-->]]>]
+ +//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > 146 > 146 1`] = ` +{ + "expected": [ + "
+ + + +//["'\`-->]]>]
", + "
+ + + + + +//["'\`-->]]>]
", + "
+ + + +//["'\`-->]]>]
", + ], + "payload": "
+ + + +//["'\`-->]]>]
", + "result": "
+ + + +//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > 157 > 157 1`] = ` +{ + "expected": [ + "
+ + +//["'\`-->]]>]
", + "
+ + +//["'\`-->]]>]
", + "
+ + +//["'\`-->]]>]
", + "
+ + +//["'\`-->]]>]
", + "
+ + +//["'\`-->]]>]
", + ], + "payload": "
+ + +//["'\`-->]]>]
", + "result": "
+ + +//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > 158 > 158 1`] = ` +{ + "expected": [ + "
+ +//["'\`-->]]>]
", + "
+ +//["'\`-->]]>]
", + "
+ +//["'\`-->]]>]
", + "
+ +//["'\`-->]]>]
", + ], + "payload": "
+ +//["'\`-->]]>]
", + "result": "
+ +//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > 172 > 172 1`] = ` +{ + "expected": [ + "
]> //["'\`-->]]>]
", + "
]> //["'\`-->]]>]
", + "
]> //["'\`-->]]>]
", + "
]> //["'\`-->]]>]
", + "
]>//["'\`-->]]>]
", + ], + "payload": "
]> //["'\`-->]]>]
", + "result": "
]> //["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > 174 > 174 1`] = ` +{ + "expected": [ + "
+ +alert(127) +//["'\`-->]]>]
", + "
+ + +//["'\`-->]]>]
", + ], + "payload": "
+ +alert(127) +//["'\`-->]]>]
", + "result": "
+ +alert(127) +//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > 175 > 175 1`] = ` +{ + "expected": [ + "
//["'\`-->]]>]
", + "
//["'\`-->]]>]
", + "
", + ], + "payload": "
//["'\`-->]]>]
", + "result": "
//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > 178 > 178 1`] = ` +{ + "expected": [ + "
+
+ +
+ +
+ + + + + + + +//["'\`-->]]>]
", + "
+
+ +
+ +
+ + + + + + + +//["'\`-->]]>]
", + ], + "payload": "
+
+ +
+ +
+ + + + + + + +//["'\`-->]]>]
", + "result": "
+
+ +
+ +
+ + + + + + + +//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > 182 > 182 1`] = ` +{ + "expected": [ + "
+ + + + +
//["'\`-->]]>]
", + "
+ + + + +
//["'\`-->]]>]
", + ], + "payload": "
+ + + + +
//["'\`-->]]>]
", + "result": "
+ + + + +
//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > Avoid over-zealous stripping of SVG filter elements (see #144) > Avoid over-zealous stripping of SVG filter elements (see #144) 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > Bypass using DOM bugs when dealing with JS URIs in arbitrary attributes (II) > Bypass using DOM bugs when dealing with JS URIs in arbitrary attributes (II) 1`] = ` +{ + "expected": "


", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > Bypass using event handlers and unknown attributes > Bypass using event handlers and unknown attributes 1`] = ` +{ + "expected": "", + "payload": "", +} +`; + +exports[`dompurify compatibility > Bypass using multiple unknown attributes > Bypass using multiple unknown attributes 1`] = ` +{ + "expected": "
@superevr
", + "payload": "
@superevr
", + "result": "
@superevr
", +} +`; + +exports[`dompurify compatibility > Bypass using unknown attributes III > Bypass using unknown attributes III 1`] = ` +{ + "expected": "
text
", + "payload": "
text", + "result": "
text
", +} +`; + +exports[`dompurify compatibility > Bypass using unknown attributes V > Bypass using unknown attributes V 1`] = ` +{ + "expected": "


", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > DOM Clobbering against an empty cookie > DOM Clobbering against an empty cookie 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > DOM Clobbering against document.createElement() (see #47) > DOM Clobbering against document.createElement() (see #47) 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > DOM clobbering XSS by @irsdl using attributes > DOM clobbering XSS by @irsdl using attributes 1`] = ` +{ + "expected": [ + "", + "
", + ], + "payload": "
", + "result": "
", +} +`; + +exports[`dompurify compatibility > DOM clobbering attack using activeElement > DOM clobbering attack using activeElement 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > DOM clobbering attack using name=body > DOM clobbering attack using name=body 1`] = ` +{ + "expected": "@mmrupp", + "payload": "@mmrupp", + "result": "@mmrupp", +} +`; + +exports[`dompurify compatibility > DOM clobbering attack using name=body and injecting SVG + keygen > DOM clobbering attack using name=body and injecting SVG + keygen 1`] = ` +{ + "expected": ", ", + "payload": ", ", + "result": ", ", +} +`; + +exports[`dompurify compatibility > DOM clobbering: acceptCharset > DOM clobbering: acceptCharset 1`] = ` +{ + "expected": "123", + "payload": "123", + "result": "123", +} +`; + +exports[`dompurify compatibility > DOM clobbering: getElementById > DOM clobbering: getElementById 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > DOM clobbering: hasChildNodes > DOM clobbering: hasChildNodes 1`] = ` +{ + "expected": "
", + "payload": "
", + "result": "
", +} +`; + +exports[`dompurify compatibility > DOM clobbering: location > DOM clobbering: location 1`] = ` +{ + "expected": "invisible", + "payload": "invisible", + "result": "invisible", +} +`; + +exports[`dompurify compatibility > DOM clobbering: submit > DOM clobbering: submit 1`] = ` +{ + "expected": "123", + "payload": "123", + "result": "123", +} +`; + +exports[`dompurify compatibility > Don't remove ARIA attributes if not prohibited (see #203) > Don't remove ARIA attributes if not prohibited (see #203) 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > Don't remove binary attributes if considered safe (see #168) > Don't remove binary attributes if considered safe (see #168) 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > Don't remove data URIs from SVG images (see #205) > Don't remove data URIs from SVG images (see #205) 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > Don't remove data URIs from SVG images, with href attribute > Don't remove data URIs from SVG images, with href attribute 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > Fixed an exception coming from missing clobbering protection > Fixed an exception coming from missing clobbering protection 1`] = ` +{ + "expected": [ + "", + "
", + ], + "payload": "
", + "result": "
", +} +`; + +exports[`dompurify compatibility > Image with data URI src > Image with data URI src 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > Image with data URI src with whitespace > Image with data URI src with whitespace 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > Img element inside noscript terminated inside attribute > Img element inside noscript terminated inside attribute 1`] = ` +{ + "expected": [ + "", + "">", + "", + "", + ], + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > Img element inside noscript terminated inside comment > Img element inside noscript terminated inside comment 1`] = ` +{ + "expected": "", + "payload": "", + "result": "", +} +`; + +exports[`dompurify compatibility > Inline SVG (data-uri) > Inline SVG (data-uri) 1`] = ` +{ + "expected": [ + "
+ +//["'\`-->]]>]
", + "
+ +//["'\`-->]]>]
", + ], + "payload": "
+ +//["'\`-->]]>]
", + "result": "
+ +//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > JavaScript URIs using Unicode LS/PS I > JavaScript URIs using Unicode LS/PS I 1`] = ` +{ + "expected": "123I am a dolphin!", + "payload": "123I am a dolphin!", + "result": "123I am a dolphin!", +} +`; + +exports[`dompurify compatibility > JavaScript URIs using Unicode LS/PS II > JavaScript URIs using Unicode LS/PS II 1`] = ` +{ + "expected": "123I am a dolphin too!", + "payload": "123I am a dolphin too!", + "result": "123I am a dolphin too!", +} +`; + +exports[`dompurify compatibility > JavaScript URIs using Unicode Whitespace > JavaScript URIs using Unicode Whitespace 1`] = ` +{ + "expected": "123CLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICK", + "payload": "123CLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICK", + "result": "123CLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICKCLICK", +} +`; + +exports[`dompurify compatibility > Low-range-ASCII obfuscated JavaScript URI > Low-range-ASCII obfuscated JavaScript URI 1`] = ` +{ + "expected": "@shafigullin", + "payload": "@shafigullin", + "result": "@shafigullin", +} +`; + +exports[`dompurify compatibility > MathML > MathML 1`] = ` +{ + "expected": [ + "
CLICKME + + + + + + +//["'\`-->]]>]
", + "
CLICKME + + +CLICKME + + +CLICKMEhttp://http://google.com +//["'\`-->]]>]
", + "
CLICKME +//["'\`-->]]>]
", + ], + "payload": "
CLICKME + + +CLICKME + + +CLICKMEhttp://http://google.com +//["'\`-->]]>]
", + "result": "
CLICKME + + +CLICKME + + +CLICKMEhttp://http://google.com +//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > MathML example > MathML example 1`] = ` +{ + "expected": [ + " + + a, + a, + a, + a, + a, + a + +", + " + + a, + a, + a, + a, + a, + a + +", + ], + "payload": " + + a, + a, + a, + a, + a, + a + +", + "result": " + + a, + a, + a, + a, + a, + a + +", +} +`; + +exports[`dompurify compatibility > SVG > SVG 1`] = ` +{ + "expected": "
+ + + +//["'\`-->]]>]
", + "payload": "
+ + + +//["'\`-->]]>]
", + "result": "
+ + + +//["'\`-->]]>]
", +} +`; + +exports[`dompurify compatibility > Special esacpes in protocol handler for XSS in Blink > Special esacpes in protocol handler for XSS in Blink 1`] = ` +{ + "expected": "@shafigullin", + "payload": "@shafigullin", + "result": "@shafigullin", +} +`; + +exports[`dompurify compatibility > Test against fake-element-based namepsace-confusion abusing mXSS attacks 1/2 > Test against fake-element-based namepsace-confusion abusing mXSS attacks 1/2 1`] = ` +{ + "expected": "a", + "payload": "a