summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html')
-rw-r--r--testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html238
1 files changed, 238 insertions, 0 deletions
diff --git a/testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html b/testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html
new file mode 100644
index 0000000000..08e3e69553
--- /dev/null
+++ b/testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html
@@ -0,0 +1,238 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script';">
+</head>
+<body>
+<div id="container"></div>
+<script id="script1">"hello world!";</script>
+<script id="trigger"></script>
+<script>
+ var container = document.querySelector("#container");
+ const policy_dict = {
+ createScript: s => (s.includes("fail") ? null : s.replace("default", "count")),
+ createHTML: s => s.replace(/2/g, "3"),
+ };
+ const policy = trustedTypes.createPolicy("policy", policy_dict);
+
+ // Regression tests for 'Bypass via insertAdjacentText', reported at
+ // https://github.com/w3c/trusted-types/issues/133
+
+ // We are trying to assert that scripts do _not_ get executed. We
+ // accomplish by having the script under examination containing a
+ // postMessage, and to send a second guaranteed-to-execute postMessage
+ // so there's a point in time when we're sure the first postMessage
+ // must have arrived (if indeed it had been sent).
+ //
+ // We'll interpret the message data as follows:
+ // - includes "block": error (this message should have been blocked by TT)
+ // - includes "count": Count these, and later check against expect_count.
+ // - includes "done": Unregister the event handler and finish the test.
+ // - all else: Reject, as this is probably an error in the test.
+ function checkMessage(expect_count) {
+ postMessage("done", "*");
+ return new Promise((resolve, reject) => {
+ let count = 0;
+ globalThis.addEventListener("message", function handler(e) {
+ if (e.data.includes("block")) {
+ reject(`'block' received (${e.data})`);
+ } else if (e.data.includes("count")) {
+ count = count + 1;
+ } else if (e.data.includes("done")) {
+ globalThis.removeEventListener("message", handler);
+ if (expect_count && count != expect_count) {
+ reject(
+ `'done' received, but unexpected counts: expected ${expect_count} != actual ${count} (${e.data})`);
+ } else {
+ resolve(e.data);
+ }
+ } else {
+ reject("unexpected message received: " + e.data);
+ }
+ });
+ });
+ }
+
+ function checkSecurityPolicyViolationEvent(expect_count) {
+ return new Promise((resolve, reject) => {
+ let count = 0;
+ document.addEventListener("securitypolicyviolation", e => {
+ if (e.sample.includes("trigger")) {
+ if (expect_count && count != expect_count) {
+ reject(
+ `'trigger' received, but unexpected counts: expected ${expect_count} != actual ${count}`);
+ } else {
+ resolve();
+ }
+ } else {
+ count = count + 1;
+ }
+ });
+ try {
+ document.getElementById("trigger").text = "trigger fail";
+ } catch(e) { }
+ });
+ }
+
+ // Original report:
+ promise_test(t => {
+ // Setup: Create a <script> element with a <p> child.
+ let s = document.createElement("script");
+
+ // Sanity check: Element child content (<p>) doesn't count as source text.
+ let p = document.createElement("p");
+ p.text = "hello('world');";
+ s.appendChild(p);
+ container.appendChild(s);
+ assert_equals(s.text, "");
+
+ // Try to insertAdjacentText into the <script>, starting from the <p>
+ p.insertAdjacentText("beforebegin",
+ "postMessage('beforebegin should be blocked', '*');");
+ assert_true(s.text.includes("postMessage"));
+ p.insertAdjacentText("afterend",
+ "postMessage('afterend should be blocked', '*');");
+ assert_true(s.text.includes("after"));
+ return checkMessage();
+ }, "Regression test: Bypass via insertAdjacentText, initial comment.");
+
+ const script_elements = {
+ "script": doc => doc.createElement("script"),
+ "svg:script": doc => doc.createElementNS("http://www.w3.org/2000/svg", "script"),
+ };
+ for (let [element, create_element] of Object.entries(script_elements)) {
+ // Like the "Original Report" test case above, but avoids use of the "text"
+ // accessor that <svg:script> doesn't support.
+ promise_test(t => {
+ let s = create_element(document);
+
+ // Sanity check: Element child content (<p>) doesn't count as source text.
+ let p = document.createElement("p");
+ p.textContent = "hello('world');";
+ s.appendChild(p);
+ container.appendChild(s);
+
+ // Try to insertAdjacentText into the <script>, starting from the <p>
+ p.insertAdjacentText("beforebegin",
+ "postMessage('beforebegin should be blocked', '*');");
+ assert_true(s.textContent.includes("postMessage"));
+ p.insertAdjacentText("afterend",
+ "postMessage('afterend should be blocked', '*');");
+ assert_true(s.textContent.includes("after"));
+ return checkMessage();
+ }, "Regression test: Bypass via insertAdjacentText, initial comment. " + element);
+
+ // Variant: Create a <script> element and create & insert a text node. Then
+ // insert it into the document container to make it live.
+ promise_test(t => {
+ // Setup: Create a <script> element, and insert text via a text node.
+ let s = create_element(document);
+ let text = document.createTextNode("postMessage('appendChild with a " +
+ "text node should be blocked', '*');");
+ s.appendChild(text);
+ container.appendChild(s);
+ return checkMessage();
+ }, "Regression test: Bypass via appendChild into off-document script element" + element);
+
+ // Variant: Create a <script> element and insert it into the document. Then
+ // create a text node and insert it into the live <script> element.
+ promise_test(t => {
+ // Setup: Create a <script> element, insert it into the doc, and then create
+ // and insert text via a text node.
+ let s = create_element(document);
+ container.appendChild(s);
+ let text = document.createTextNode("postMessage('appendChild with a live " +
+ "text node should be blocked', '*');");
+ s.appendChild(text);
+ return checkMessage();
+ }, "Regression test: Bypass via appendChild into live script element " + element);
+ }
+
+ promise_test(t => {
+ return checkSecurityPolicyViolationEvent();
+ }, "Prep for subsequent tests: Clear SPV event queue.");
+
+ promise_test(t => {
+ const inserted_script = document.getElementById("script1");
+ assert_throws_js(TypeError, _ => {
+ inserted_script.innerHTML = "2+2";
+ });
+
+ let new_script = document.createElement("script");
+ assert_throws_js(TypeError, _ => {
+ new_script.innerHTML = "2+2";
+ container.appendChild(new_script);
+ });
+
+ const trusted_html = policy.createHTML("2*4");
+ assert_equals("" + trusted_html, "3*4");
+ inserted_script.innerHTML = trusted_html;
+ assert_equals(inserted_script.textContent, "3*4");
+
+ new_script = document.createElement("script");
+ new_script.innerHTML = trusted_html;
+ container.appendChild(new_script);
+ assert_equals(inserted_script.textContent, "3*4");
+
+ // We expect 3 SPV events: two for the two assert_throws_js cases, and one
+ // for script element, which will be rejected at the time of execution.
+ return checkSecurityPolicyViolationEvent(3);
+ }, "Spot tests around script + innerHTML interaction.");
+
+
+ // Create default policy. Wrapped in a promise_test to ensure it runs only
+ // after the other tests.
+ let default_policy;
+ promise_test(t => {
+ default_policy = trustedTypes.createPolicy("default", policy_dict);
+ return Promise.resolve();
+ }, "Prep for subsequent tests: Create default policy.");
+
+ for (let [element, create_element] of Object.entries(script_elements)) {
+ promise_test(t => {
+ let s = create_element(document);
+ let text = document.createTextNode("postMessage('default', '*');");
+ s.appendChild(text);
+ container.appendChild(s);
+ return Promise.all([checkMessage(1),
+ checkSecurityPolicyViolationEvent(0)]);
+ }, "Test that default policy applies. " + element);
+
+ promise_test(t => {
+ let s = create_element(document);
+ let text = document.createTextNode("fail");
+ s.appendChild(text);
+ container.appendChild(s);
+ return Promise.all([checkMessage(0),
+ checkSecurityPolicyViolationEvent(1)]);
+ }, "Test a failing default policy. " + element);
+ }
+
+ promise_test(t => {
+ const inserted_script = document.getElementById("script1");
+ inserted_script.innerHTML = "2+2";
+ assert_equals(inserted_script.textContent, "3+3");
+
+ let new_script = document.createElement("script");
+ new_script.innerHTML = "2+2";
+ container.appendChild(new_script);
+ assert_equals(inserted_script.textContent, "3+3");
+
+ const trusted_html = default_policy.createHTML("2*4");
+ assert_equals("" + trusted_html, "3*4");
+ inserted_script.innerHTML = trusted_html;
+ assert_equals(inserted_script.textContent, "3*4");
+
+ new_script = document.createElement("script");
+ new_script.innerHTML = trusted_html;
+ container.appendChild(new_script);
+ assert_equals(inserted_script.textContent, "3*4");
+
+ return checkSecurityPolicyViolationEvent(0);
+ }, "Spot tests around script + innerHTML interaction with default policy.");
+</script>
+</body>
+</html>
+