Skip to content

Commit 4f1bf2e

Browse files
committed
ShadowRoot.innerHTML - update to support TrustedHTML
1 parent 21c690a commit 4f1bf2e

File tree

1 file changed

+96
-6
lines changed
  • files/en-us/web/api/shadowroot/innerhtml

1 file changed

+96
-6
lines changed

files/en-us/web/api/shadowroot/innerhtml/index.md

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,118 @@ browser-compat: api.ShadowRoot.innerHTML
88

99
{{APIRef("Shadow DOM")}}
1010

11+
> [!WARNING]
12+
> This property parses its input as HTML, writing the result into the DOM.
13+
> APIs like this are known as [injection sinks](/en-US/docs/Web/API/Trusted_Types_API#concepts_and_usage), and are potentially a vector for [cross-site-scripting (XSS)](/en-US/docs/Web/Security/Attacks/XSS) attacks, if the input originally came from an attacker.
14+
>
15+
> You can mitigate this risk by always assigning `TrustedHTML` objects instead of strings and [enforcing trusted types](/en-US/docs/Web/API/Trusted_Types_API#using_a_csp_to_enforce_trusted_types).
16+
> See [Security considerations](#security_considerations) for more information.
17+
1118
The **`innerHTML`** property of the {{domxref("ShadowRoot")}} interface gets or sets the HTML markup to the DOM tree inside the `ShadowRoot`.
1219

20+
## Value
21+
22+
Getting the property returns a string containing the HTML serialization of the shadow root's descendants.
23+
24+
Setting the property accepts either a {{domxref("TrustedHTML")}} object or a string.
25+
It parses this value as HTML and replaces all the element's descendants with the result.
26+
When set to the `null` value, that `null` value is converted to the empty string (`""`), so `shadowRoot.innerHTML = null` is equivalent to `shadowRoot.innerHTML = ""`.
27+
28+
### Exceptions
29+
30+
- `SyntaxError` {{domxref("DOMException")}}
31+
- : Thrown if an attempt was made to set the value of `innerHTML` using a string which is not properly-formed HTML.
32+
- `TypeError`
33+
- : Thrown if the property is set to a string when [Trusted Types](/en-US/docs/Web/API/Trusted_Types_API) are [enforced by a CSP](/en-US/docs/Web/API/Trusted_Types_API#using_a_csp_to_enforce_trusted_types) and no default policy is defined.
34+
35+
## Description
36+
37+
`innerHTML` gets a serialization of the nested child DOM elements within the shadow root, or sets HTML or XML that should be parsed to replace the DOM tree within the shadow root.
38+
1339
Note that some browsers serialize the `<` and `>` characters as `&lt;` and `&gt;` when they appear in attribute values (see [Browser compatibility](#browser_compatibility)).
1440
This is to prevent a potential security vulnerability ([mutation XSS](https://www.securitum.com/mutation-xss-via-mathml-mutation-dompurify-2-0-17-bypass.html)) in which an attacker can craft input that bypasses a [sanitization function](/en-US/docs/Web/Security/Attacks/XSS#sanitization), enabling a cross-site scripting (XSS) attack.
1541

16-
## Value
42+
### Security considerations
1743

18-
A string.
44+
The `innerHTML` property is a possible vector for [Cross-site-scripting (XSS)](/en-US/docs/Web/Security/Attacks/XSS) attacks, where potentially unsafe strings provided by a user are injected into the DOM without first being sanitized.
45+
While the property does prevent {{HTMLElement("script")}} elements from executing when they are injected, it is susceptible to many other ways that attackers can craft HTML to run malicious JavaScript.
46+
For example, the following example would execute the code in the `error` event handler, because the {{htmlelement("img")}} `src` value is not a valid image URL:
1947

20-
When set to the `null` value, that `null` value is converted to the empty string (`""`), so `sr.innerHTML = null` is equivalent to `sr.innerHTML = ""`.
48+
```js
49+
const name = "<img src='x' onerror='alert(1)'>";
50+
shadowRoot.innerHTML = name; // shows the alert
51+
```
52+
53+
You can mitigate these issues by always assigning {{domxref("TrustedHTML")}} objects instead of strings, and [enforcing trusted type](/en-US/docs/Web/API/Trusted_Types_API#using_a_csp_to_enforce_trusted_types) using the [`require-trusted-types-for`](/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/require-trusted-types-for) CSP directive.
54+
This ensures that the input is passed through a transformation function, which has the chance to [sanitize](/en-US/docs/Web/Security/Attacks/XSS#sanitization) the input to remove potentially dangerous markup before it is injected.
2155

2256
## Examples
2357

58+
### Reading the HTML contents of an element
59+
60+
Reading `innerHTML` causes the user agent to serialize the shadow root's descendants.
61+
62+
Given the following HTML:
63+
64+
```html
65+
<div class="host">
66+
<template shadowrootmode="open">
67+
<p>My name is Joe</p>
68+
</template>
69+
</div>
70+
```
71+
72+
You can get and log the markup for the shadow root as shown:
73+
74+
```js
75+
const shadowHost = document.querySelector("#host");
76+
const shadowRoot = shadowHost.shadowRoot;
77+
const contents = shadowRoot.innerHTML;
78+
console.log(contents); // "\n <p>My name is Joe</p>\n"
79+
```
80+
2481
### Setting the innerHTML of a Shadow root
2582

83+
In this example we'll replace an element's DOM by assigning HTML to the element's `innerHTML` property.
84+
To mitigate the risk of XSS, we'll first create a `TrustedHTML` object from the string containing the HTML, and then assign that object to `innerHTML`.
85+
86+
Trusted types are not yet supported on all browsers, so first we define the [trusted types tinyfill](/en-US/docs/Web/API/Trusted_Types_API#trusted_types_tinyfill).
87+
This acts as a transparent replacement for the trusted types JavaScript API:
88+
2689
```js
27-
let customElem = document.querySelector("my-shadow-dom-element");
28-
let shadow = customElem.shadowRoot;
90+
if (typeof trustedTypes === "undefined")
91+
trustedTypes = { createPolicy: (n, rules) => rules };
92+
```
2993

30-
shadow.innerHTML = "<strong>This element should be more important!</strong>";
94+
Next we create a {{domxref("TrustedTypePolicy")}} that defines a {{domxref("TrustedTypePolicy/createHTML", "createHTML()")}} for transforming an input string into {{domxref("TrustedHTML")}} instances.
95+
Commonly implementations of `createHTML()` use a library such as [DOMPurify](https://github.com/cure53/DOMPurify) to sanitize the input as shown below:
96+
97+
```js
98+
const policy = trustedTypes.createPolicy("my-policy", {
99+
createHTML: (input) => DOMPurify.sanitize(input),
100+
});
31101
```
32102

103+
Then we use this `policy` object to create a `TrustedHTML` object from the potentially unsafe input string, and assign the result to the element:
104+
105+
```js
106+
// The potentially malicious string
107+
const untrustedString = "<p>I might be XSS</p><img src='x' onerror='alert(1)'>";
108+
109+
// Create a TrustedHTML instance using the policy
110+
const trustedHTML = policy.createHTML(untrustedString);
111+
112+
// Get the shadow root
113+
const shadowHost = document.querySelector("#host");
114+
const shadowRoot = shadowHost.shadowRoot;
115+
116+
// Inject the TrustedHTML (which contains a trusted string)
117+
shadowRoot.innerHTML = trustedHTML;
118+
```
119+
120+
> [!WARNING]
121+
> While you can directly assign a string to `innerHTML` this is a [security risk](#security_considerations) if the string to be inserted might contain potentially malicious content.
122+
33123
## Specifications
34124

35125
{{Specifications}}

0 commit comments

Comments
 (0)