fix(trusted-types): gate execCommand HTML input

This commit is contained in:
ldm0
2026-09-23 00:11:51 +08:00
parent 76997cf263
commit a4e14e1894
4 changed files with 100 additions and 3 deletions
@@ -3617,7 +3617,6 @@ trusted-types/ServiceWorkerContainer-register-from-SharedWorker.https.html
trusted-types/TrustedType-AttributeNodes.html
trusted-types/TrustedTypePolicyFactory-getAttributeType.html
trusted-types/Window-TrustedTypes.html
trusted-types/block-Document-execCommand.html
trusted-types/block-string-assignment-to-Document-write.html
trusted-types/block-string-assignment-to-Element-outerHTML.html
trusted-types/block-string-assignment-to-Element-setAttribute.html
@@ -3650,7 +3649,6 @@ trusted-types/trusted-types-event-handlers.html
trusted-types/trusted-types-report-only.html
trusted-types/trusted-types-reporting-check-report-DedicatedWorker-sink-mismatch.html
trusted-types/trusted-types-reporting-for-DedicatedWorker-ServiceWorkerContainer-register.https.html
trusted-types/trusted-types-reporting-for-Document-execCommand.html
trusted-types/trusted-types-reporting-for-Document-write.html
trusted-types/trusted-types-reporting-for-Element-setAttribute.html
trusted-types/trusted-types-reporting-for-HTMLIFrameElement-srcdoc.html
@@ -8597,6 +8597,7 @@ trusted-types/TrustedTypePolicyFactory-getAttributeType-svg.html
trusted-types/TrustedTypePolicyFactory-isXXX.html
trusted-types/Window-block-eval-function-constructor.html
trusted-types/Window-setTimeout-setInterval.html
trusted-types/block-Document-execCommand.html
trusted-types/block-string-assignment-to-DOMParser-parseFromString.html
trusted-types/block-string-assignment-to-DedicatedWorker-setTimeout-setInterval.html
trusted-types/block-string-assignment-to-Document-parseHTMLUnsafe.html
@@ -8666,6 +8667,7 @@ trusted-types/trusted-types-reporting-for-DedicatedWorker-eval.html
trusted-types/trusted-types-reporting-for-DedicatedWorker-function-constructor.html
trusted-types/trusted-types-reporting-for-DedicatedWorker-importScripts.html
trusted-types/trusted-types-reporting-for-DedicatedWorker-setTimeout-setInterval.html
trusted-types/trusted-types-reporting-for-Document-execCommand.html
trusted-types/trusted-types-reporting-for-Document-parseHTMLUnsafe.html
trusted-types/trusted-types-reporting-for-Element-innerHTML.html
trusted-types/trusted-types-reporting-for-Element-insertAdjacentHTML.html
@@ -537,7 +537,7 @@ pub(in crate::native_bridge) fn node_document_exec_command_callback<'s>(
return;
}
EditingCommand::InsertHtml => {
let Some(value) = editing_command_value(scope, &args) else {
let Some(value) = editing_command_insert_html_value(scope, runtime_ptr, &args) else {
return;
};
let inserted = exec_command_insert_html(scope, runtime_ptr, &value);
@@ -759,6 +759,24 @@ fn editing_command_value<'s>(
.map(|value| value.to_rust_string_lossy(scope))
}
fn editing_command_insert_html_value<'s>(
scope: &mut v8::PinScope<'s, '_>,
runtime_ptr: *mut JsContextHost,
args: &v8::FunctionCallbackArguments<'s>,
) -> Option<String> {
if args.length() < 3 || args.get(2).is_undefined() {
return Some(String::new());
}
let requirements = unsafe { &*runtime_ptr }.trusted_types_for_script_requirements(scope);
crate::context_bootstrap::trusted_html_string_or_throw(
scope,
args.get(2),
requirements,
"Document execCommand",
"execCommand",
)
}
fn exec_command_insert_html(
scope: &mut v8::PinScope<'_, '_>,
runtime_ptr: *mut JsContextHost,
@@ -661,6 +661,85 @@ fn dom_parser_gates_converted_union_source_after_webidl_argument_conversion() {
);
}
#[test]
fn document_exec_command_gates_only_insert_html_values() {
let mut vm = new_storage_test_vm("https://exec-command-trusted-types.test/");
vm.set_response_content_security_policies(&["require-trusted-types-for 'script'".to_owned()]);
let result = vm
.eval(
r#"
(() => {
const errorName = callback => {
try {
callback();
return "none";
} catch (error) {
return error && error.name;
}
};
const root = document.documentElement || document.appendChild(document.createElement("html"));
const body = document.body || root.appendChild(document.createElement("body"));
const input = document.createElement("input");
body.append(input);
input.focus();
const blocked = [
errorName(() => document.execCommand("insertHTML", false, "<b>blocked</b>")),
errorName(() => document.execCommand("InSeRtHtMl", false, null))
];
const unprotected = [
errorName(() => document.execCommand("insertHTML")),
errorName(() => document.execCommand("insertHTML", false, undefined)),
errorName(() => document.execCommand("paste", false, "<b>plain</b>")),
errorName(() => document.execCommand("insertText", false, "plain"))
];
input.value = "";
const custom = trustedTypes.createPolicy("exec-command-custom", {
createHTML: value => value
});
const trustedReturned = document.execCommand(
"insertHTML",
false,
custom.createHTML("<b>trusted</b>")
);
const trustedValue = input.value;
let sourceConversions = 0;
const defaultCalls = [];
trustedTypes.createPolicy("default", {
createHTML: (value, type, sink) => {
defaultCalls.push([value, type, sink]);
return "<b>default</b>";
}
});
input.value = "";
const defaultReturned = document.execCommand("insertHTML", false, {
toString() {
sourceConversions += 1;
return "<i>original</i>";
}
});
return JSON.stringify({
blocked,
unprotected,
trusted: [trustedReturned, trustedValue],
defaulted: [defaultReturned, input.value, sourceConversions],
defaultCalls
});
})()
"#,
)
.expect("Document.execCommand TrustedHTML sink probe should evaluate");
assert_eq!(
result,
r#"{"blocked":["TypeError","TypeError"],"unprotected":["none","none","none","none"],"trusted":[true,"trusted"],"defaulted":[true,"default",1],"defaultCalls":[["<i>original</i>","TrustedHTML","Document execCommand"]]}"#
);
}
#[test]
fn document_write_gates_concatenated_union_values_before_writeln_newline() {
let mut vm = new_storage_test_vm("https://document-write-trusted-types.test/");