ShadowRoot: phương thức setHTMLUnsafe()
Baseline
2025
Newly available
Since September 2025, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.
Warning: Phương thức này phân tích đầu vào của nó như HTML và ghi kết quả vào DOM. Các API như thế này được biết đến như injection sinks, và có thể là một vector cho các cuộc tấn công cross-site scripting (XSS) nếu đầu vào ban đầu đến từ kẻ tấn công.
Bạn có thể giảm thiểu rủi ro này bằng cách luôn truyền TrustedHTML thay vì chuỗi và ép buộc trusted types.
Xem Cân nhắc bảo mật để biết thêm thông tin.
Note:
ShadowRoot.setHTML() gần như luôn nên được dùng thay cho phương thức này - trên các trình duyệt hỗ trợ nó - vì nó luôn loại bỏ các phần tử HTML không an toàn trước XSS.
Phương thức setHTMLUnsafe() của giao diện ShadowRoot có thể được dùng để phân tích đầu vào HTML thành một DocumentFragment, tùy chọn lọc các phần tử và thuộc tính không mong muốn, rồi dùng fragment đó để thay thế cây hiện có trong Shadow DOM.
Cú pháp
setHTMLUnsafe(input)
setHTMLUnsafe(input, options)
Tham số
input-
Một thể hiện
TrustedHTMLhoặc một chuỗi định nghĩa HTML sẽ được phân tích cú pháp. optionsOptional-
Một đối tượng tùy chọn với các tham số tùy chọn sau:
sanitizerOptional-
Một đối tượng
SanitizerhoặcSanitizerConfigxác định phần tử nào trong đầu vào được cho phép hoặc bị loại bỏ. Nó cũng có thể là chuỗi có giá trị"default", áp dụng mộtSanitizervới cấu hình sanitizer mặc định (an toàn trước XSS). Nếu không chỉ định, sẽ không dùng sanitizer nào.Lưu ý rằng nếu bạn dùng cùng một cấu hình nhiều lần, thường sẽ hiệu quả hơn khi dùng một
Sanitizervà sửa đổi nó khi cần.
Giá trị trả về
Không có (undefined).
Ngoại lệ
TypeError-
Được ném ra nếu:
inputđược truyền một chuỗi khi Trusted Types được ép buộc bởi CSP và không có chính sách mặc định nào được định nghĩa.options.sanitizerđược truyền:- một
SanitizerConfigkhông hợp lệ. Ví dụ, một cấu hình chứa đồng thời cài đặt "allowed" và "removed". - chuỗi không có giá trị
"default". - giá trị không phải là
Sanitizer,SanitizerConfig, hoặc chuỗi.
- một
Mô tả
Phương thức setHTMLUnsafe() có thể được dùng để phân tích một chuỗi HTML, tùy chọn lọc các phần tử và thuộc tính không mong muốn, rồi dùng nó để thay thế Shadow DOM hiện có.
Không giống như ShadowRoot.innerHTML, các declarative shadow root trong đầu vào sẽ được phân tích vào DOM. Nếu chuỗi HTML xác định nhiều hơn một declarative shadow root trong một shadow host cụ thể thì chỉ shadow root đầu tiên được tạo ra - các khai báo sau sẽ được phân tích thành các phần tử <template> bên trong shadow root đó.
setHTMLUnsafe() không thực hiện làm sạch nào theo mặc định. Nếu không truyền sanitizer nào làm tham số, toàn bộ phần tử HTML trong đầu vào sẽ được chèn vào.
Cân nhắc bảo mật
Hậu tố "Unsafe" trong tên phương thức cho biết rằng nó không cưỡng chế việc loại bỏ mọi phần tử HTML không an toàn trước XSS (khác với ShadowRoot.setHTML()). Mặc dù nó có thể làm như vậy nếu được dùng với một sanitizer phù hợp, nhưng nó không bắt buộc phải dùng sanitizer hiệu quả, hoặc thậm chí không dùng sanitizer nào cả! Vì vậy, phương thức này là một vector tiềm năng cho các cuộc tấn công cross-site scripting (XSS), nơi các chuỗi có thể không an toàn do người dùng cung cấp được chèn vào DOM mà không được làm sạch trước.
Bạn nên giảm thiểu rủi ro này bằng cách luôn truyền đối tượng TrustedHTML thay vì chuỗi, và ép buộc trusted types bằng chỉ thị CSP require-trusted-types-for. Điều này đảm bảo đầu vào được đưa qua một hàm chuyển đổi, hàm này có cơ hội làm sạch đầu vào để loại bỏ markup có thể nguy hiểm (như phần tử <script> và các thuộc tính xử lý sự kiện), trước khi nó được chèn vào.
Sử dụng TrustedHTML giúp có thể kiểm tra và xác minh rằng mã làm sạch chỉ tồn tại ở một vài nơi, thay vì rải khắp mọi injection sink.
Khi dùng TrustedHTML, bạn không cần truyền sanitizer vào phương thức.
Nếu vì bất kỳ lý do nào bạn không thể dùng TrustedHTML (hoặc tốt hơn nữa là setHTML()) thì lựa chọn an toàn tiếp theo là dùng setHTMLUnsafe() với cấu hình sanitizer mặc định an toàn trước XSS.
Khi nào nên dùng setHTMLUnsafe()?
setHTMLUnsafe() gần như không bao giờ nên được dùng nếu ShadowRoot.setHTML() khả dụng, vì rất hiếm khi (nếu có) đầu vào HTML do người dùng cung cấp lại cần chứa các phần tử không an toàn trước XSS.
Không chỉ setHTML() an toàn, mà nó còn giúp bạn không phải cân nhắc đến trusted types.
Dùng setHTMLUnsafe() có thể phù hợp nếu:
-
Bạn không thể dùng
setHTML()hoặc trusted types (vì bất kỳ lý do gì) và bạn muốn lọc một cách an toàn nhất có thể. Trong trường hợp này, bạn có thể dùngsetHTMLUnsafe()với cấu hình sanitizer mặc định để lọc các phần tử không an toàn trước XSS và các phần tử có vấn đề khác. -
Bạn không thể dùng
setHTML()và đầu vào có thể chứa declarative shadow roots, nên bạn không thể dùngShadowRoot.innerHTML. -
Bạn có một trường hợp đặc biệt mà bạn buộc phải cho phép đầu vào HTML chứa một tập hợp đã biết gồm các phần tử HTML không an toàn.
Trong trường hợp này bạn không thể dùng
setHTML()vì nó loại bỏ mọi phần tử không an toàn. Bạn có thể dùngsetHTMLUnsafe()không kèm sanitizer hoặcinnerHTML, nhưng như vậy sẽ cho phép toàn bộ các phần tử không an toàn.Một lựa chọn tốt hơn ở đây là gọi
setHTMLUnsafe()với sanitizer chỉ cho phép những phần tử và thuộc tính nguy hiểm mà thực sự cần. Dù vẫn không an toàn, nó vẫn an toàn hơn việc cho phép tất cả.
Cho điểm cuối cùng, hãy xem xét một tình huống trong đó mã của bạn dựa vào việc sử dụng được các trình xử lý onclick không an toàn. Đoạn mã sau cho thấy tác động của các phương thức và sanitizer khác nhau trong trường hợp này.
const shadow = document.querySelector("#host").shadowRoot;
const input = "<img src=x onclick=alert('onclick') onerror=alert('onerror')>";
// An toàn - loại bỏ mọi phần tử không an toàn trước XSS.
shadow.setHTML(input);
// Không loại bỏ các thuộc tính xử lý sự kiện
shadow.setHTMLUnsafe(input);
shadow.innerHTML = input;
// An toàn - loại bỏ mọi phần tử không an toàn trước XSS.
const configSafe = new Sanitizer();
shadow.setHTMLUnsafe(input, { sanitizer: configSafe });
// Loại bỏ mọi phần tử không an toàn trước XSS trừ `onclick`
const configLessSafe = new Sanitizer();
config.allowAttribute("onclick");
shadow.setHTMLUnsafe(input, { sanitizer: configLessSafe });
Ví dụ
>setHTMLUnsafe() với Trusted Types
Để giảm thiểu rủi ro XSS, trước tiên chúng ta sẽ tạo một đối tượng TrustedHTML từ chuỗi chứa HTML, rồi truyền đối tượng đó cho setHTMLUnsafe(). Vì trusted types chưa được hỗ trợ trên tất cả trình duyệt, chúng ta định nghĩa trusted types tinyfill. Nó đóng vai trò thay thế trong suốt cho Trusted Types JavaScript API:
if (typeof trustedTypes === "undefined")
trustedTypes = { createPolicy: (n, rules) => rules };
Tiếp theo, chúng ta tạo một TrustedTypePolicy định nghĩa createHTML() để chuyển đổi chuỗi đầu vào thành các thể hiện TrustedHTML. Thông thường, các triển khai createHTML() sẽ dùng một thư viện như DOMPurify để làm sạch đầu vào như minh họa dưới đây:
const policy = trustedTypes.createPolicy("my-policy", {
createHTML: (input) => DOMPurify.sanitize(input),
});
Sau đó chúng ta dùng policy này để tạo một đối tượng TrustedHTML từ chuỗi đầu vào có thể không an toàn:
// Chuỗi có khả năng độc hại
const untrustedString = "abc <script>alert(1)<" + "/script> def";
// Tạo thể hiện TrustedHTML bằng policy
const trustedHTML = policy.createHTML(untrustedString);
Bây giờ đã có trustedHTML, đoạn mã dưới đây cho thấy bạn có thể dùng nó với setHTMLUnsafe() như thế nào. Trước tiên, chúng ta tạo ShadowRoot mà mình muốn thao tác. Nó có thể được tạo theo lập trình bằng Element.attachShadow(), nhưng trong ví dụ này chúng ta sẽ tạo root theo kiểu khai báo.
<div id="host">
<template shadowrootmode="open">
<span>A span element in the shadow DOM</span>
</template>
</div>
Sau đó chúng ta lấy shadow root từ phần tử #host và gọi setHTMLUnsafe(). Đầu vào đã đi qua hàm chuyển đổi, nên chúng ta không truyền sanitizer cho phương thức.
const shadow = document.querySelector("#host").shadowRoot;
// setHTMLUnsafe() không có sanitizer (không lọc)
shadow.setHTMLUnsafe(trustedHTML);
Dùng setHTMLUnsafe() mà không có Trusted Types
Ví dụ này minh họa trường hợp chúng ta không dùng trusted types, nên sẽ truyền các đối số sanitizer.
Đoạn mã trước tiên tạo một chuỗi không đáng tin cậy và cho thấy một số cách sanitizer có thể được truyền vào phương thức.
// Chuỗi có khả năng độc hại
const untrustedString = "abc <script>alert(1)<" + "/script> def";
// Lấy shadow root element
const shadow = document.querySelector("#host").shadowRoot;
// Định nghĩa Sanitizer tùy chỉnh và dùng trong setHTMLUnsafe()
// Điều này chỉ cho phép các phần tử: div, p, button, script
const sanitizer1 = new Sanitizer({
elements: ["div", "p", "button", "script"],
});
shadow.setHTMLUnsafe(untrustedString, { sanitizer: sanitizer1 });
// Định nghĩa SanitizerConfig tùy chỉnh trong setHTMLUnsafe()
// Xóa phần tử <script> nhưng cho phép các phần tử không an toàn khác.
shadow.setHTMLUnsafe(untrustedString, {
sanitizer: { removeElements: ["script"] },
});
Ví dụ trực tiếp của setHTMLUnsafe()
Ví dụ này cung cấp một minh họa "trực tiếp" của phương thức khi được gọi với các sanitizer khác nhau. Mã định nghĩa các nút mà bạn có thể nhấp để chèn một chuỗi HTML. Một nút chèn HTML mà không làm sạch gì cả, và nút thứ hai dùng một sanitizer tùy chỉnh cho phép các phần tử <script> nhưng không cho phép các mục không an toàn khác. Chuỗi gốc và HTML đã chèn được ghi log để bạn có thể kiểm tra kết quả ở mỗi trường hợp.
Note: Vì chúng ta muốn minh họa cách dùng đối số sanitizer, đoạn mã sau chèn một chuỗi thay vì một trusted type. Trong mã sản xuất, bạn không nên làm như vậy.
HTML
HTML định nghĩa hai phần tử <button> để chèn HTML không có sanitizer và với một sanitizer tùy chỉnh (tương ứng), một nút khác để đặt lại ví dụ, và một <div> chứa declarative shadow root.
<button id="buttonNoSanitizer" type="button">None</button>
<button id="buttonAllowScript" type="button">allowScript</button>
<button id="reload" type="button">Reload</button>
<div id="host">
<template shadowrootmode="open">
<span>I am in the shadow DOM </span>
</template>
</div>
JavaScript
Trước hết chúng ta định nghĩa trình xử lý cho nút tải lại.
const reload = document.querySelector("#reload");
reload.addEventListener("click", () => document.location.reload());
Sau đó chúng ta định nghĩa chuỗi đầu vào sẽ được chèn vào shadow root, chuỗi này sẽ giống nhau cho mọi trường hợp. Chuỗi này chứa phần tử <script> và trình xử lý onclick, cả hai đều bị xem là không an toàn trước XSS. Chúng ta cũng lấy biến shadow, là handle của shadow root.
// Định nghĩa chuỗi HTML không an toàn
const unsanitizedString = `
<div>
<p>Paragraph to inject into shadow DOM.
<button onclick="alert('You clicked the button!')">Click me</button>
</p>
<script src="path/to/a/module.js" type="module"><\/script>
<p data-id="123">Para with <code>data-</code> attribute</p>
</div>
`;
const shadow = document.querySelector("#host").shadowRoot;
Tiếp theo chúng ta định nghĩa trình xử lý click cho nút đặt shadow root bằng setHTMLUnsafe() mà không truyền sanitizer. Vì không có sanitizer, ta kỳ vọng HTML được chèn sẽ khớp với chuỗi đầu vào.
const buttonNoSanitizer = document.querySelector("#buttonNoSanitizer");
buttonNoSanitizer.addEventListener("click", () => {
// Đặt nội dung của phần tử mà không có sanitizer
shadow.setHTMLUnsafe(unsanitizedString);
// Ghi log HTML trước khi làm sạch và sau khi được chèn
logElement.textContent = "No sanitizer\n\n";
log(`\nunsanitized: ${unsanitizedString}`);
log(`\n\nsanitized: ${shadow.innerHTML}`);
});
Trình xử lý click tiếp theo đặt HTML mục tiêu bằng một sanitizer tùy chỉnh chỉ cho phép các phần tử <div>, <p>, và <script>.
const allowScriptButton = document.querySelector("#buttonAllowScript");
allowScriptButton.addEventListener("click", () => {
// Đặt nội dung của phần tử bằng sanitizer tùy chỉnh
const sanitizer1 = new Sanitizer({
elements: ["div", "p", "script"],
});
shadow.setHTMLUnsafe(unsanitizedString, { sanitizer: sanitizer1 });
// Ghi log HTML trước khi làm sạch và sau khi được chèn
logElement.textContent = "Sanitizer: {elements: ['div', 'p', 'script']}\n";
log(`\n\nunsanitized: ${unsanitizedString}`);
log(`\nsanitized: ${shadow.innerHTML}`);
});
Kết quả
Nhấp vào các nút "None" và "allowScript" để xem tác động của việc không dùng sanitizer và dùng một sanitizer tùy chỉnh tương ứng.
Khi bạn nhấp vào nút "None", bạn sẽ thấy đầu vào và đầu ra khớp nhau, vì không áp dụng sanitizer.
Khi bạn nhấp vào nút "allowScript", phần tử <script> vẫn còn, nhưng phần tử <button> bị loại bỏ.
Với cách này, bạn có thể tạo HTML an toàn, nhưng bạn không bị buộc phải làm vậy.
Thông số kỹ thuật
| Specification |
|---|
| HTML> # dom-shadowroot-sethtmlunsafe> |