Element: replaceWithHTMLUnsafe() method
Limited availability
This feature is not Baseline because it does not work in some of the most widely-used browsers.
Want more browser support for this feature? Tell us why.
Warning: This method parses its input as HTML, writing the result into the DOM. APIs like this are known as injection sinks, and are potentially a vector for cross-site scripting (XSS) attacks, if the input originally came from an attacker.
You can mitigate this risk by always passing TrustedHTML objects instead of strings and enforcing trusted types.
See Security considerations for more information.
Note:
Element.replaceWithHTML() should almost always be used instead of this method — on browsers where it is supported — as it always removes XSS-unsafe HTML entities.
The replaceWithHTMLUnsafe() method of the Element interface is used to parse HTML input into a DocumentFragment, optionally filtering out unwanted elements and attributes, and those that don't belong in the context, and then using it to replace the element in the DOM.
The method does nothing if the node has no parent.
Note that the safe variants of these methods (for example, Element.replaceWithHTML) always removes any XSS-unsafe elements or attributes — whether or not they are allowed by the sanitizer. The provided sanitizer is applied in addition to the default XSS-unsafe removal, whereas with Unsafe methods any provided sanitizer is the only sanitizer used.
Syntax
replaceWithHTMLUnsafe(input)
replaceWithHTMLUnsafe(input, options)
Parameters
input-
A
TrustedHTMLinstance or string defining HTML to be parsed. optionsOptional-
An options object with the following optional parameters:
sanitizerOptional-
A
SanitizerorSanitizerConfigobject that defines what elements of the input will be allowed or removed. This can also be a string with the value"default", which applies aSanitizerwith the (XSS-safe) default sanitizer configuration. If not specified, no sanitizer is used.Note that if you're using the same configuration multiple times, it's expected to be more efficient to use a
Sanitizerand modify it when you need to. runScriptsOptional-
A boolean value indicating whether any scripts included in the newly inserted HTML should run after inserting. The default is
false.
Return value
None (undefined).
Exceptions
TypeError-
This is thrown if:
inputis passed a string when Trusted Types are enforced by a CSP and no default policy is defined.options.sanitizeris passed a:SanitizerConfigthat isn't valid. For example, a configuration that includes both "allowed" and "removed" configuration settings.- string that does not have the value
"default". - value that is not a
Sanitizer,SanitizerConfig, or string.
options.runScriptsistrueoroptions.sanitizeris specified when Trusted Types are enforced and no default policy definescreateParserOptions— even if input is aTrustedHTML. (Passing aTrustedHTMLParserOptionsas options avoids this.)
HierarchyRequestErrorDOMException-
Thrown when the parent element is a
Document, an<html>element, a<template>element, or a hostlessDocumentFragment. Note this differs fromElement.insertAdjacentHTMLwhich throwsNoModificationAllowedErrorfor aDocumentparent.
Description
The replaceWithHTMLUnsafe() method is used to parse an HTML input into a DocumentFragment, optionally sanitizing it of unwanted elements and attributes, and discarding elements that the HTML specification doesn't allow in the target element (<col> element outside of a <table>).
The DocumentFragment is then used to replace the element in the DOM.
Unlike with Element.innerHTML, declarative shadow roots in the input will be parsed into the DOM.
If the string of HTML defines more than one declarative shadow root in a particular shadow host then only the first ShadowRoot is created — subsequent declarations are parsed as <template> elements within that shadow root.
replaceWithHTMLUnsafe() doesn't perform any sanitization by default, though also does not run scripts by default. If no sanitizer is passed as a parameter, all HTML entities in the input will be injected. It is therefore potentially even less safe than Element.innerHTML, which does not allow <script> execution when parsing.
Related HTML setter APIs
This is part of a series of APIs to set HTML in Element:
| Action | API | Unsafe API |
|---|---|---|
| Set the HTML contents of the element | setHTML |
setHTMLUnsafe |
| Replace the entire element with this HTML | replaceWithHTML |
replaceWithHTMLUnsafe |
| Add the HTML before the element | beforeHTML |
beforeHTMLUnsafe |
| Add the HTML as the first child of the element | prependHTML |
prependHTMLUnsafe |
| Add the HTML as the last child of the element | appendHTML |
appendHTMLUnsafe |
| Add the HTML after the element | afterHTML |
afterHTMLUnsafe |
Each API type takes the same arguments but allows for ease of inserting the required HTML in the appropriate place and with or without the default sanitizer applied. The unsafe variants allows scripts to be run using the runScripts options.
Security considerations
The suffix "Unsafe" in the method name indicates that it does not enforce removal of all XSS-unsafe HTML entities (unlike Element.replaceWithHTML()).
While it can do so if used with an appropriate sanitizer, it doesn't have to use an effective sanitizer, or any sanitizer at all!
The method is therefore a possible vector for cross-site scripting (XSS) attacks, where potentially unsafe strings provided by a user are injected into the DOM without first being sanitized.
You should mitigate this risk by always passing TrustedHTML objects instead of strings, and enforcing trusted types using the require-trusted-types-for CSP directive.
This ensures that the input is passed through a transformation function, which has the chance to sanitize the input to remove potentially dangerous markup (such as <script> elements and event handler attributes), before it is injected.
Using TrustedHTML makes it possible to audit and check that sanitization code is effective in just a few places, rather than scattered across all your injection sinks.
You should not have to pass a sanitizer to the method when using TrustedHTML.
If for any reason you can't use TrustedHTML (or even better, replaceWithHTML()) then the next safest option is to use replaceWithHTMLUnsafe() with the default sanitizer configuration.
When should replaceWithHTMLUnsafe() be used?
replaceWithHTMLUnsafe() should almost never be used if Element.replaceWithHTML() is available, because there are very few (if any) cases where user-provided HTML input should need to include XSS-unsafe elements.
Not only is replaceWithHTML() safe, but it avoids having to consider trusted types.
Using replaceWithHTMLUnsafe() might be appropriate if:
-
You can't use
replaceWithHTML()or trusted types (for whatever reason) and you want to have the safest possible filtering. In this case you might usereplaceWithHTMLUnsafe()with the defaultSanitizerto filter all XSS-unsafe elements. -
You can't use
replaceWithHTML()and the input might contain declarative shadow roots, so you can't useElement.innerHTML. -
You have an edge case where you have to allow HTML input that includes a known set of unsafe HTML entities.
You can't use
replaceWithHTML()in this case, because it strips all unsafe entities. You could usereplaceWithHTMLUnsafe()without a sanitizer orinnerHTML, but that would allow all unsafe entities.A better option here is to call
replaceWithHTMLUnsafe()with a sanitizer that allows just those dangerous elements and attributes we actually need. While this is still unsafe, it is safer than allowing all of them.
For the last point, consider a situation where your code relies on being able to use unsafe onclick handlers.
The following code shows the effect of the different methods and sanitizers for this case.
const target = document.querySelector("#target");
const input = "<img src=x onclick=alert('onclick') onerror=alert('onerror')>";
// Safe - removes all XSS-unsafe entities.
target.replaceWithHTML(input);
// Removes no event handler attributes
target.replaceWithHTMLUnsafe(input);
// Safe - removes all XSS-unsafe entities.
const configSafe = new Sanitizer();
target.replaceWithHTMLUnsafe(input, { sanitizer: configSafe });
// Removes all XSS-unsafe entities except `onclick`
const configLessSafe = new Sanitizer();
configLessSafe.allowAttribute("onclick");
target.replaceWithHTMLUnsafe(input, { sanitizer: configLessSafe });
Examples
Note:
The setHTMLUnsafe() page includes additional examples, including a live example, which are similar to how replaceWithHTMLUnsafe() can be used.
replaceWithHTMLUnsafe() with Trusted Types
To mitigate the risk of XSS, we'll first create a TrustedHTML object from the string containing the HTML, and then pass that object to replaceWithHTMLUnsafe().
Since trusted types are not yet supported on all browsers, we define the trusted types tinyfill.
This acts as a transparent replacement for the Trusted Types JavaScript API:
if (typeof trustedTypes === "undefined")
trustedTypes = { createPolicy: (n, rules) => rules };
Next we create a TrustedTypePolicy that defines a createHTML() for transforming an input string into TrustedHTML instances.
Commonly implementations of createHTML() use a library such as DOMPurify to sanitize the input as shown below:
const policy = trustedTypes.createPolicy("my-policy", {
createHTML: (input) => DOMPurify.sanitize(input),
});
Then we use this policy object to create a TrustedHTML object from the potentially unsafe input string:
// The potentially malicious string
const untrustedString = "abc <script>alert(1)<" + "/script> def";
// Create a TrustedHTML instance using the policy
const trustedHTML = policy.createHTML(untrustedString);
Now that we have trustedHTML, the code below shows how you can use it with replaceWithHTMLUnsafe().
The input has been through the transformation function, so we don't pass a sanitizer to the method.
// Get the target Element with id "target"
const target = document.getElementById("target");
// replaceWithHTMLUnsafe() with no sanitizer
target.replaceWithHTMLUnsafe(trustedHTML);
Using replaceWithHTMLUnsafe() without Trusted Types
This example demonstrates the case where we aren't using trusted types, so we'll be passing sanitizer arguments.
The code creates an untrusted string and shows a number of ways a sanitizer can be passed to the method.
// The potentially malicious string
const untrustedString = "abc <script>alert(1)<" + "/script> def";
// Get the target Element with id "target"
const target = document.getElementById("target");
// Define custom Sanitizer and use in replaceWithHTMLUnsafe()
// This allows only elements: div, p, button, script
const sanitizer1 = new Sanitizer({
elements: ["div", "p", "button", "script"],
});
target.replaceWithHTMLUnsafe(untrustedString, { sanitizer: sanitizer1 });
// Define custom SanitizerConfig within replaceWithHTMLUnsafe()
// Removes the <script> element but allows other potentially unsafe entities.
target.replaceWithHTMLUnsafe(untrustedString, {
sanitizer: { removeElements: ["script"] },
});
Using replaceWithHTMLUnsafe() with runScripts
This example demonstrates the case when we allow scripts to run using the runScripts option.
// The potentially malicious string
const untrustedString =
"<script src='example.js'></script><p>Other content</p>";
// Get the target Element with id "target"
const target = document.getElementById("target");
// No sanitizer is passed, so the <script> element is kept
// and also executes the script after insertion.
target.replaceWithHTMLUnsafe(untrustedString, {
runScripts: true,
});
Specifications
| Specification |
|---|
| HTML> # dom-nondocumenttypechildnode-replacewithhtmlunsafe> |