Skip to content

Commit

Permalink
Update Sanitizer & Trusted Types API polyfills
Browse files Browse the repository at this point in the history
...Which mean numerous breaking changes throughout.
  • Loading branch information
shgysk8zer0 committed Apr 3, 2024
1 parent 10934ff commit 06df963
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 273 deletions.
1 change: 0 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"no-prototype-builtins": 0
},
"globals": {
"Sanitizer": "readonly",
"AggregateError": "readonly",
"cookieStore": "readonly",
"globalThis": "readonly",
Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [v1.0.0] - 2024-04-02

### Changed
- Updated to more recent polyfills for Sanitizer API & Trusted Types API
- Massive breaking changes in multiple places

## [v0.3.4] - 2024-03-15

### Added
Expand Down Expand Up @@ -149,7 +155,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Add `markdown` module
- Add `isSafeHTML()` to `trust-policies` module (checks for unsafe elements & attributes)
- Add `()` to `trust-policies` module (checks for unsafe elements & attributes)
- Add `getCSSStyleSheet()` to `http` module
- Add Markdown to `types` module
- Add `gravatar` module
Expand Down
26 changes: 8 additions & 18 deletions dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,30 +239,20 @@ export function text(what, text, { base } = {}) {

export function html(what, text, {
base,
sanitizer,
policy = 'trustedTypes' in globalThis ? globalThis.trustedTypes.defaultPolicy : null,
allowElements,
allowComments,
allowAttributes,
allowCustomElements,
blockElements,
dropAttributes,
dropElements,
allowUnknownMarkup,
sanitizer: {
elements,
attributes,
comments,
} = {}
} = {}) {
if (
Element.prototype.setHTML instanceof Function
&& typeof allowAttributes !== 'undefined'
&& typeof allowElements !== 'undefined'
&& typeof attributes !== 'undefined'
&& typeof elements !== 'undefined'
) {
const tmp = document.createElement('div');
tmp.setHTML({
allowComments, allowAttributes, allowCustomElements, blockElements,
dropAttributes, dropElements, allowUnknownMarkup,
});
return each(what, el => el.append(...tmp.cloneNode(true).children), { base });
} else if (typeof sanitizer !== 'undefined' && Element.prototype.setHTML instanceof Function) {
const tmp = tmp.setHTML(text, { sanitizer });
tmp.setHTML({ sanitizer: { elements, attributes, comments }});
return each(what, el => el.append(...tmp.cloneNode(true).children), { base });
} else if (isHTML(text)) {
return each(what, el => el.innerHTML = text, { base });
Expand Down
27 changes: 8 additions & 19 deletions elements.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @copyright 2023 Chris Zuber <admin@kernvalley.us>
* @copyright 2023-2024 Chris Zuber <admin@kernvalley.us>
*/
import { data, attr, css, getAttrs, aria as setAria } from './attrs.js';
import { listen } from './events.js';
Expand Down Expand Up @@ -76,15 +76,11 @@ export function createElement(tag, {
text = undefined,
html = undefined,
policy = 'trustedTypes' in globalThis ? globalThis.trustedTypes.defaultPolicy : null,
sanitizer = undefined,
allowElements,
allowComments,
allowAttributes,
allowCustomElements,
blockElements,
dropAttributes,
dropElements,
allowUnknownMarkup,
sanitizer: {
elements,
comments,
attributes,
} = {},
aria = undefined,
events: { capture, passive, once, signal, ...events } = {},
animation: {
Expand Down Expand Up @@ -150,16 +146,9 @@ export function createElement(tag, {
} else if (typeof html === 'string' || isHTML(html)) {
if (
Element.prototype.setHTML instanceof Function
&& (
(typeof allowElements !== 'undefined' && typeof allowAttributes !== 'undefined')
|| ('Sanitizer' in globalThis && sanitizer instanceof globalThis.Sanitizer)
)
&& (typeof elements !== 'undefined' && typeof attributes !== 'undefined')
) {
el.setHTML(html, {
allowElements, allowComments, allowAttributes,
allowCustomElements, blockElements, dropAttributes, dropElements,
allowUnknownMarkup, sanitizer,
});
el.setHTML(html, { sanitizer: { elements, attributes, comments }});
} else {
setProp(el, 'innerHTML', html, { policy });
}
Expand Down
21 changes: 7 additions & 14 deletions http.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @copyright 2021-2023 Chris Zuber <admin@kernvalley.us>
* @copyright 2021-2024 Chris Zuber <admin@kernvalley.us>
*/
import { parse } from './dom.js';
import { signalAborted } from './abort.js';
Expand Down Expand Up @@ -229,15 +229,11 @@ export async function getHTML(url, {
keepalive = undefined,
signal = undefined,
timeout = null,
sanitizer = undefined,
allowElements,
allowComments,
allowAttributes,
allowCustomElements,
blockElements,
dropAttributes,
dropElements,
allowUnknownMarkup,
sanitizer: {
elements,
attributes,
comments,
} = {},
policy,
errorMessage,
} = {}) {
Expand All @@ -251,10 +247,7 @@ export async function getHTML(url, {
return frag;
} else {
const frag = document.createDocumentFragment();
const doc = Document.parseHTML(html, {
sanitizer, allowElements, allowComments, allowAttributes, allowCustomElements,
blockElements, dropAttributes, dropElements, allowUnknownMarkup,
});
const doc = Document.parseHTML(html, { sanitizer: { elements, attributes, comments }});
frag.append(...doc.head.children, ...doc.body.children);
return frag;
}
Expand Down
18 changes: 9 additions & 9 deletions markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ use(markedHighlight({

export async function parseMarkdown(str, {
base = document.baseURI,
allowElements = ALLOW_ELEMENTS,
allowAttributes = ALLOW_ATTRIBUTES,
elements = ALLOW_ELEMENTS,
attributes = ALLOW_ATTRIBUTES,
} = {}) {
const { resolve, reject, promise } = Promise.withResolvers();

requestIdleCallback(() => {
try {
const parsed = parse(str);
const doc = Document.parseHTML(parsed, { allowElements, allowAttributes });
const doc = Document.parseHTML(parsed, { sanitizer: { elements, attributes }});
const frag = document.createDocumentFragment();

doc.querySelectorAll('img').forEach(img => {
Expand All @@ -74,23 +74,23 @@ export async function parseMarkdown(str, {

export async function parseMarkdownFile(file, {
base = document.baseURI,
allowElements = ALLOW_ELEMENTS,
allowAttributes = ALLOW_ATTRIBUTES,
elements = ALLOW_ELEMENTS,
attributes = ALLOW_ATTRIBUTES,
} = {}) {
if (! (file instanceof File)) {
throw new TypeError('Not a file.');
} else if (file.type !== MARKDOWN) {
throw new TypeError(`${file.name} is not a markdown file.`);
} else {
const text = await file.text();
return await parseMarkdown(text, { base, allowElements, allowAttributes });
return await parseMarkdown(text, { base, elements, attributes });
}
}

export async function fetchMarkdown(url, {
base = document.baseURI,
allowElements = ALLOW_ELEMENTS,
allowAttributes = ALLOW_ATTRIBUTES,
elements = ALLOW_ELEMENTS,
attributes = ALLOW_ATTRIBUTES,
headers = {},
mode = 'cors',
cache = 'default',
Expand All @@ -111,6 +111,6 @@ export async function fetchMarkdown(url, {
throw new TypeError(`Expected "Content-Type: ${MARKDOWN}" but got ${resp.headers.get('Content-Type')}.`);
} else {
const text = await resp.text();
return await parseMarkdown(text, { base, allowElements, allowAttributes });
return await parseMarkdown(text, { base, elements, attributes });
}
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shgysk8zer0/kazoo",
"version": "0.3.4",
"version": "1.0.0",
"private": false,
"type": "module",
"description": "A JavaScript monorepo for all the things!",
Expand Down
59 changes: 39 additions & 20 deletions test/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" dir="ltr" data-trusted-policies="empty#html empty#script custom#html sanitizer-raw#html blob#script-url youtube#script-url krv#embed">
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
Expand All @@ -22,7 +22,7 @@
connect-src 'self' https://unpkg.com/ https://api.github.com/users/ https://api.github.com/repos/ https://api.pwnedpasswords.com/range/ https://maps.kernvalley.us/places/ https://events.kernvalley.us/events.json https://events.kernvalley.us/cal/krv-events.ics https://whiskeyflatdays.com/events.json https://whiskeyflatdays.com/mayors/events.json;
img-src * data: blob:;
require-trusted-types-for 'script';
trusted-types empty#html empty#script default sanitizer-raw#html trust-raw#html youtube#script-url github-user#html github-repo#html blob#script-url goog-cal#script-url goog-maps#script-url krv#embed;
trusted-types default aegis-sanitizer#html youtube#script-url github-user#html github-repo#html blob#script-url goog-cal#script-url goog-maps#script-url krv#embed;
upgrade-insecure-requests;"
/>
<title>Kazoo</title>
Expand All @@ -31,29 +31,48 @@
{
"imports": {
"@shgysk8zer0/kazoo/": "../",
"@shgysk8zer0/konami": "https://unpkg.com/@shgysk8zer0/konami@1.0.10/konami.js",
"@shgysk8zer0/polyfills": "https://unpkg.com/@shgysk8zer0/polyfills@0.3.0/all.min.js",
"@shgysk8zer0/polyfills/": "https://unpkg.com/@shgysk8zer0/polyfills@0.3.0/",
"@shgysk8zer0/konami": "https://unpkg.com/@shgysk8zer0/konami@1.1.1/konami.js",
"@shgysk8zer0/polyfills": "https://unpkg.com/@shgysk8zer0/polyfills@0.3.7/all.min.js",
"@shgysk8zer0/polyfills/": "https://unpkg.com/@shgysk8zer0/polyfills@0.3.7/",
"@shgysk8zer0/jswaggersheets": "https://unpkg.com/@shgysk8zer0/jswaggersheets@1.1.0/swagger.js",
"@shgysk8zer0/jswaggersheets/": "https://unpkg.com/@shgysk8zer0/jswaggersheets@1.1.0/",
"@shgysk8zer0/jss/": "https://unpkg.com/@shgysk8zer0/jss@1.0.1/",
"@shgysk8zer0/consts/": "https://unpkg.com/@shgysk8zer0/consts@1.0.8/",
"@shgysk8zer0/jswaggersheets": "https://unpkg.com/@shgysk8zer0/jswaggersheets@1.0.4/swagger.js",
"@shgysk8zer0/jswaggersheets/": "https://unpkg.com/@shgysk8zer0/jswaggersheets@1.0.4/",
"@shgysk8zer0/http-status": "https://unpkg.com/@shgysk8zer0/http-status@1.0.2/http-status.js",
"@shgysk8zer0/components/": "https://unpkg.com/@shgysk8zer0/components@0.0.12/",
"@kernvalley/components/": "https://unpkg.com/@kernvalley/components@1.1.2/",
"@shgysk8zer0/http/": "https://unpkg.com/@shgysk8zer0/http@1.0.5/",
"@shgysk8zer0/http-status": "https://unpkg.com/@shgysk8zer0/http-status@1.1.1/http-status.js",
"@aegisjsproject/trusted-types": "https://unpkg.com/@aegisjsproject/trusted-types@1.0.1/bundle.min.js",
"@aegisjsproject/trusted-types/": "https://unpkg.com/@aegisjsproject/trusted-types@1.0.1/",
"@aegisjsproject/parsers": "https://unpkg.com/@aegisjsproject/parsers@0.0.6/bundle.min.js",
"@aegisjsproject/parsers/": "https://unpkg.com/@aegisjsproject/parsers@0.0.6/",
"@aegisjsproject/sanitizer": "https://unpkg.com/@aegisjsproject/sanitizer@0.0.9/sanitizer.js",
"@aegisjsproject/sanitizer/": "https://unpkg.com/@aegisjsproject/sanitizer@0.0.9/",
"@aegisjsproject/core": "https://unpkg.com/@aegisjsproject/core@0.2.5/core.js",
"@aegisjsproject/core/": "https://unpkg.com/@aegisjsproject/core@0.2.5/",
"@aegisjsproject/styles": "https://unpkg.com/@aegisjsproject/styles@0.1.1/styles.js",
"@aegisjsproject/styles/": "https://unpkg.com/@aegisjsproject/styles@0.1.1/",
"@aegisjsproject/component": "https://unpkg.com/@aegisjsproject/component@0.1.2/component.js",
"@aegisjsproject/component/": "https://unpkg.com/@aegisjsproject/component@0.1.2/",
"@aegisjsproject/markdown": "https://unpkg.com/@aegisjsproject/markdown@0.1.2/markdown.js",
"@aegisjsproject/markdown/": "https://unpkg.com/@aegisjsproject/markdown@0.1.2/",
"@aegisjsproject/aegis-md": "https://unpkg.com/@aegisjsproject/aegis-md@0.0.2/aegis-md.js",
"@aegisjsproject/aegis-md/": "https://unpkg.com/@aegisjsproject/aegis-md@0.0.2/",
"@shgysk8zer0/components/": "https://unpkg.com/@shgysk8zer0/components@0.1.6/",
"@kernvalley/components/": "https://unpkg.com/@kernvalley/components@1.1.5/",
"@webcomponents/custom-elements": "https://unpkg.com/@webcomponents/custom-elements@1.6.0/custom-elements.min.js",
"leaflet": "https://unpkg.com/leaflet@1.9.4/dist/leaflet-src.esm.js",
"firebase/": "https://www.gstatic.com/firebasejs/9.22.2/",
"urlpattern-polyfill": "https://unpkg.com/urlpattern-polyfill@9.0.0/index.js",
"highlight.js": "https://unpkg.com/@highlightjs/cdn-assets@11.8.0/es/highlight.min.js",
"highlight.js/": "https://unpkg.com/@highlightjs/cdn-assets@11.8.0/",
"marked": "https://unpkg.com/marked@5.1.0/src/marked.js",
"marked-highlight": "https://unpkg.com/marked-highlight@2.0.1/src/index.js"
},
"scope": {}
"urlpattern-polyfill": "https://unpkg.com/urlpattern-polyfill@10.0.0/index.js",
"highlight.js": "https://unpkg.com/@highlightjs/cdn-assets@11.9.0/es/core.min.js",
"highlight.js/": "https://unpkg.com/@highlightjs/cdn-assets@11.9.0/es/",
"@highlightjs/cdn-assets": "https://unpkg.com/@highlightjs/cdn-assets@11.9.0/es/core.min.js",
"@highlightjs/cdn-assets/": "https://unpkg.com/@highlightjs/cdn-assets@11.9.0/es/",
"marked": "https://unpkg.com/marked@12.0.1/lib/marked.esm.js",
"marked-highlight": "https://unpkg.com/marked-highlight@2.1.1/src/index.js",
"firebase/": "https://www.gstatic.com/firebasejs/9.23.0/"
}
}
</script>
<script type="application/javascript" defer="" referrerpolicy="no-referrer" crossorigin="anonymous" integrity="sha384-h2kgNESJV1KN7BmHtXZv1j5WMcwFojESsbaNydwul3t6bqHOmzVMDaDXAEe6XLC+" src="https://unpkg.com/@shgysk8zer0/polyfills@0.3.0/all.min.js" fetchpriority="high"></script>
<script src="/harden.js" referrerpolicy="no-referrer" defer=""></script>
<script referrerpolicy="no-referrer" crossorigin="anonymous" integrity="sha384-kmIBBdQxerXwHwY0debVvlTDCJiBQBPsk5luyQ2y++Ly3aoL0ot4FGDpC+HLNYzp" src="https://unpkg.com/@shgysk8zer0/polyfills@0.3.8/all.min.js" fetchpriority="high" defer=""></script>
<script type="module" src="./js/policy.js" referrerpolicy="no-referrer"></script>
<script type="module" src="./js/index.js" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="./css/index.css" referrerpolicy="no-referrer" media="all" />
<link rel="icon" type="image/svg+xml" sizes="any" href="./favicon.svg" />
Expand Down
20 changes: 1 addition & 19 deletions test/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { createElement } from '../../elements.js';
import { getJSON, getHTML } from '../../http.js';
import { html, attr, each } from '../../dom.js';
import { animate } from '../../animate.js';
import { createPolicy } from '../../trust.js';
import { isTrustedScriptOrigin } from '../../trust-policies.js';
import { createYouTubeEmbed } from '../../youtube.js';
import { createSVGFile } from '../../svg.js';
import * as icons from '../../icons.js';
Expand All @@ -18,22 +16,6 @@ import { addStyle } from '@shgysk8zer0/jswaggersheets';
import '@shgysk8zer0/components/github/user.js';
import '@shgysk8zer0/components/github/repo.js';

const policy = createPolicy('default', {
createHTML: input => {
const el = document.createElement('div');
el.setHTML(input);
return el.innerHTML;
},
createScript: () => trustedTypes.emptyScript,
createScriptURL: input => {
if (isTrustedScriptOrigin(input)) {
return input;
} else {
throw new DOMException(`Untrusted Script URL: ${input}`);
}
}
});

addStyle(document, btnStyles);

each('[data-template]', el =>
Expand All @@ -56,7 +38,7 @@ document.getElementById('footer').append(
);

getJSON('./api/bacon.json').then(async lines => {
html('#bacon', policy.createHTML(lines.map(t => `<p onclick="alert(1)">${t}</p>`).join('')));
html('#bacon', trustedTypes.defaultPolicy.createHTML(lines.map(t => `<p onclick="alert(1)">${t}</p>`).join('')));

document.getElementById('header')
.append(...Object.entries(icons).map(([ariaLabel, func]) => func({ size: 64, ariaLabel })));
Expand Down
18 changes: 18 additions & 0 deletions test/js/policy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { isTrustedScriptOrigin } from '../../trust-policies.js';
// import { createPolicy } from '../../trust.js';

trustedTypes.createPolicy('default', {
createHTML: input => {
const el = document.createElement('div');
el.setHTML(input);
return el.innerHTML;
},
createScript: () => trustedTypes.emptyScript,
createScriptURL: input => {
if (isTrustedScriptOrigin(input)) {
return input;
} else {
throw new DOMException(`Untrusted Script URL: ${input}`);
}
}
});

0 comments on commit 06df963

Please sign in to comment.