From 10220c9af132149c31cd0539273aad1be251fcb4 Mon Sep 17 00:00:00 2001 From: Peter Burns Date: Thu, 26 Aug 2021 12:37:17 -0700 Subject: [PATCH] Add support for TrustedTypes (#5692) * Add support for TrustedTypes to html-tag.js This avoids setting innerHTML to a string. * Comment on why .slice() the XMLSerializer output * Handle data binding Trusted Types into attributes Fixes #5648 * Lint clean --- .eslintrc.json | 3 +- lib/mixins/property-accessors.js | 16 ++++++++ lib/utils/html-tag.js | 64 ++++++++++++++++++++++++++++---- 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 77b19fd82b..b20d2dc908 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -40,6 +40,7 @@ "Polymer": true, "ShadyDOM": true, "ShadyCSS": true, - "JSCompiler_renameProperty": true + "JSCompiler_renameProperty": true, + "trustedTypes": true } } diff --git a/lib/mixins/property-accessors.js b/lib/mixins/property-accessors.js index 2590165c03..b1d45b3046 100644 --- a/lib/mixins/property-accessors.js +++ b/lib/mixins/property-accessors.js @@ -26,6 +26,14 @@ while (proto) { proto = Object.getPrototypeOf(proto); } +const isTrustedType = (() => { + if (!window.trustedTypes) { + return () => false; + } + return (val) => trustedTypes.isHTML(val) || + trustedTypes.isScript(val) || trustedTypes.isScriptURL(val); +})(); + /** * Used to save the value of a property that will be overridden with * an accessor. If the `model` is a prototype, the values will be saved @@ -215,6 +223,14 @@ export const PropertyAccessors = dedupingMixin(superClass => { if (value instanceof Date) { return value.toString(); } else if (value) { + if (isTrustedType(value)) { + /** + * Here `value` isn't actually a string, but it should be + * passed into APIs that normally expect a string, like + * elem.setAttribute. + */ + return /** @type {?} */ (value); + } try { return JSON.stringify(value); } catch(x) { diff --git a/lib/utils/html-tag.js b/lib/utils/html-tag.js index 3b0e3def9e..bde13b5171 100644 --- a/lib/utils/html-tag.js +++ b/lib/utils/html-tag.js @@ -9,13 +9,33 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN */ import './boot.js'; +/** + * Our TrustedTypePolicy for HTML which is declared using the Polymer html + * template tag function. + * + * That HTML is a developer-authored constant, and is parsed with innerHTML + * before any untrusted expressions have been mixed in. Therefor it is + * considered safe by construction. + * + * @type {!TrustedTypePolicy|undefined} + */ +const policy = window.trustedTypes && + trustedTypes.createPolicy('polymer-html-literal', {createHTML: (s) => s}); + /** * Class representing a static string value which can be used to filter * strings by asseting that they have been created via this class. The * `value` property returns the string passed to the constructor. */ class LiteralString { - constructor(string) { + /** + * @param {!ITemplateArray} strings Constant parts of tagged template literal + * @param {!Array<*>} values Variable parts of tagged template literal + */ + constructor(strings, values) { + assertValidTemplateStringParameters(strings, values); + const string = values.reduce( + (acc, v, idx) => acc + literalValue(v) + strings[idx + 1], strings[0]); /** @type {string} */ this.value = string.toString(); } @@ -48,7 +68,15 @@ function literalValue(value) { */ function htmlValue(value) { if (value instanceof HTMLTemplateElement) { - return /** @type {!HTMLTemplateElement } */(value).innerHTML; + // Use the XML serializer to avoid xMSS attacks from browsers' sometimes + // unexpected formatting / cleanup of innerHTML. + const serializedNewTree = new XMLSerializer().serializeToString( + /** @type {!HTMLTemplateElement } */ (value)); + // The XMLSerializer is similar to .outerHTML, so slice off the leading + // and trailing parts of the