fix(trusted-types): enforce policy creation CSP

This commit is contained in:
ldm0
2026-09-09 06:38:21 +08:00
parent 3394eadb47
commit 28f1e852f2
11 changed files with 385 additions and 86 deletions
@@ -3690,8 +3690,6 @@ trusted-types/ServiceWorkerContainer-register-from-DedicatedWorker.https.html
trusted-types/ServiceWorkerContainer-register-from-ServiceWorker.https.html
trusted-types/ServiceWorkerContainer-register-from-SharedWorker.https.html
trusted-types/TrustedType-AttributeNodes.html
trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none.html
trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests.html
trusted-types/TrustedTypePolicyFactory-getAttributeType-namespace.html
trusted-types/TrustedTypePolicyFactory-getAttributeType-svg.html
trusted-types/TrustedTypePolicyFactory-getAttributeType.html
@@ -3723,17 +3721,11 @@ trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-001.html
trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-002.html
trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-003.html
trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.html
trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-005.html
trusted-types/trusted-types-duplicate-names-list.html
trusted-types/trusted-types-duplicate-names-without-enforcement.html
trusted-types/trusted-types-duplicate-names.html
trusted-types/trusted-types-eval-reporting-no-unsafe-eval.html
trusted-types/trusted-types-eval-reporting-report-only.html
trusted-types/trusted-types-event-handlers.html
trusted-types/trusted-types-report-only.html
trusted-types/trusted-types-reporting-check-report-DedicatedWorker-create-policy.html
trusted-types/trusted-types-reporting-check-report-DedicatedWorker-sink-mismatch.html
trusted-types/trusted-types-reporting-clipping-of-sample.html
trusted-types/trusted-types-reporting-for-DOMParser-parseFromString.html
trusted-types/trusted-types-reporting-for-DedicatedWorker-ServiceWorkerContainer-register.https.html
trusted-types/trusted-types-reporting-for-Document-execCommand.html
@@ -7810,7 +7810,9 @@ trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-noNamesGiven.html
trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none-none-name.html
trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none-none.html
trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none-skip.html
trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-none.html
trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-wildcard.html
trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests.html
trusted-types/TrustedTypePolicyFactory-createPolicy-non-tt-policy-name.html
trusted-types/TrustedTypePolicyFactory-createPolicy-unenforced.html
trusted-types/TrustedTypePolicyFactory-defaultPolicy.html
@@ -7871,8 +7873,14 @@ trusted-types/script-enforcement-017.html
trusted-types/set-attributes-no-require-trusted-types.html
trusted-types/should-sink-type-mismatch-violation-be-blocked-by-csp-001.html
trusted-types/should-sink-type-mismatch-violation-be-blocked-by-csp-003.html
trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-005.html
trusted-types/trusted-types-createHTMLDocument.html
trusted-types/trusted-types-duplicate-names-list-report-only.html
trusted-types/trusted-types-duplicate-names-list.html
trusted-types/trusted-types-duplicate-names-without-enforcement.html
trusted-types/trusted-types-duplicate-names.html
trusted-types/trusted-types-reporting-check-report-DedicatedWorker-create-policy.html
trusted-types/trusted-types-reporting-clipping-of-sample.html
trusted-types/trusted-types-reporting-for-DedicatedWorker-DedicatedWorker-constructor.html
trusted-types/trusted-types-reporting-for-DedicatedWorker-eval.html
trusted-types/trusted-types-reporting-for-DedicatedWorker-function-constructor.html
+80 -30
View File
@@ -316,15 +316,6 @@ pub(crate) fn content_security_policy_allows_trusted_types_eval(policies: &[Stri
.any(|policy| policy_allows_trusted_types_eval(policy))
}
pub(crate) fn content_security_policy_allows_trusted_type_policy_name(
policies: &[String],
policy_name: &str,
) -> bool {
policies
.iter()
.all(|policy| policy_allows_trusted_type_policy_name(policy, policy_name))
}
pub(crate) fn content_security_policy_sandboxes_document_domain(policies: &[String]) -> bool {
policies
.iter()
@@ -972,6 +963,41 @@ pub(crate) fn content_security_policy_trusted_types_sink_violation_with_disposit
})
}
pub(crate) fn content_security_policy_trusted_types_policy_violation_with_disposition_and_reporting_endpoints(
policies: &[String],
protected_url: &Url,
policy_name: &str,
is_duplicate: bool,
disposition: ContentSecurityPolicyDisposition,
reporting_endpoints: &ContentSecurityPolicyReportingEndpoints,
) -> Option<ContentSecurityPolicyUrlViolation> {
policies.iter().find_map(|policy| {
if policy_allows_trusted_type_policy_name(policy, policy_name, is_duplicate) {
return None;
}
let document_uri = protected_url.to_string();
Some(ContentSecurityPolicyUrlViolation {
effective_directive: TRUSTED_TYPES,
blocked_uri: "trusted-types-policy".to_owned(),
source_file: document_uri.clone(),
document_uri,
original_policy: policy.clone(),
disposition,
report_uri_endpoints: content_security_policy_report_uri_endpoints(
policy,
protected_url,
),
report_to_endpoints: content_security_policy_report_to_endpoints(
policy,
reporting_endpoints,
),
sample: trusted_types_violation_sample(policy_name),
line_number: 0,
column_number: 0,
})
})
}
pub(crate) fn content_security_policy_non_url_violation_with_disposition_and_reporting_endpoints(
policy: &str,
protected_url: &Url,
@@ -1146,19 +1172,28 @@ fn policy_allows_trusted_types_eval(policy: &str) -> bool {
})
}
fn policy_allows_trusted_type_policy_name(policy: &str, policy_name: &str) -> bool {
fn policy_allows_trusted_type_policy_name(
policy: &str,
policy_name: &str,
is_duplicate: bool,
) -> bool {
let directives = parsed_directives(policy);
let Some(sources) = directive_source_list(&directives, TRUSTED_TYPES) else {
return true;
};
sources.iter().any(|source| {
let name_is_allowed = sources.iter().any(|source| {
let source = source.trim();
source == "*"
|| (!source.is_empty()
&& !csp_keyword_eq(source, "none")
&& !csp_keyword_eq(source, "allow-duplicates")
&& source == policy_name)
})
});
let duplicate_is_allowed = !is_duplicate
|| sources
.iter()
.any(|source| csp_keyword_eq(source.trim(), "allow-duplicates"));
name_is_allowed && duplicate_is_allowed
}
fn policy_sandboxes_document_domain(policy: &str) -> bool {
@@ -1212,10 +1247,14 @@ fn sandbox_sources_allow_popups_to_escape(sources: &[&str]) -> bool {
}
fn trusted_types_sink_violation_sample(sink: &str, sample: &str) -> String {
let clipped = sample.chars().take(40).collect::<String>();
let clipped = trusted_types_violation_sample(sample);
format!("{sink}|{clipped}")
}
fn trusted_types_violation_sample(sample: &str) -> String {
sample.chars().take(40).collect()
}
fn effective_source_list_with_directive(
policy: &str,
kind: ContentSecurityPolicyResourceKind,
@@ -3299,27 +3338,38 @@ mod tests {
#[test]
fn trusted_types_directive_filters_policy_names() {
let allowed = |policies: &[&str], name: &str| {
content_security_policy_allows_trusted_type_policy_name(
&policies
.iter()
.map(|policy| policy.to_string())
.collect::<Vec<_>>(),
name,
)
let allowed = |policies: &[&str], name: &str, is_duplicate: bool| {
policies
.iter()
.all(|policy| policy_allows_trusted_type_policy_name(policy, name, is_duplicate))
};
assert!(allowed(&[], "SomeName"));
assert!(allowed(&["default-src 'none'"], "SomeName"));
assert!(allowed(&["trusted-types SomeName OtherName"], "SomeName"));
assert!(allowed(&["trusted-types * 'aLLow-dUPLIcates'"], "SomeName"));
assert!(allowed(&["trusted-types 'none' SomeName"], "SomeName"));
assert!(!allowed(&["trusted-types"], "SomeName"));
assert!(!allowed(&["trusted-types 'nONe'"], "SomeName"));
assert!(!allowed(&["trusted-types SomeName"], "default"));
assert!(allowed(&[], "SomeName", false));
assert!(allowed(&[], "SomeName", true));
assert!(allowed(&["default-src 'none'"], "SomeName", false));
assert!(allowed(
&["trusted-types SomeName OtherName"],
"SomeName",
false
));
assert!(allowed(
&["trusted-types * 'aLLow-dUPLIcates'"],
"SomeName",
true
));
assert!(allowed(
&["trusted-types 'none' SomeName"],
"SomeName",
false
));
assert!(!allowed(&["trusted-types"], "SomeName", false));
assert!(!allowed(&["trusted-types 'nONe'"], "SomeName", false));
assert!(!allowed(&["trusted-types SomeName"], "SomeName", true));
assert!(!allowed(&["trusted-types SomeName"], "default", false));
assert!(!allowed(
&["trusted-types SomeName", "trusted-types OtherName"],
"SomeName"
"SomeName",
false
));
}
@@ -31,6 +31,7 @@ const TRUSTED_TYPES_CREATE_HTML_SLOT: &str = "__moliTrustedTypesCreateHTML";
const TRUSTED_TYPES_CREATE_SCRIPT_SLOT: &str = "__moliTrustedTypesCreateScript";
const TRUSTED_TYPES_CREATE_SCRIPT_URL_SLOT: &str = "__moliTrustedTypesCreateScriptURL";
const TRUSTED_TYPES_POLICY_NAME_SLOT: &str = "__moliTrustedTypesPolicyName";
const TRUSTED_TYPES_CREATED_POLICY_NAMES_SLOT: &str = "__moliTrustedTypesCreatedPolicyNames";
const TRUSTED_TYPES_EMPTY_HTML_SLOT: &str = "__moliTrustedTypesEmptyHTML";
const TRUSTED_TYPES_EMPTY_SCRIPT_SLOT: &str = "__moliTrustedTypesEmptyScript";
const TRUSTED_TYPES_EMPTY_VALUE_SLOTS: [&str; 2] = [
@@ -102,6 +103,8 @@ struct TrustedTypesFactoryObjectDeclaration<'scope> {
empty_html: v8::Local<'scope, v8::Object>,
#[webapi(slot = TRUSTED_TYPES_EMPTY_SCRIPT_SLOT)]
empty_script: v8::Local<'scope, v8::Object>,
#[webapi(slot = TRUSTED_TYPES_CREATED_POLICY_NAMES_SLOT)]
created_policy_names: v8::Local<'scope, v8::Array>,
}
#[derive(WebApiObject)]
@@ -861,7 +864,8 @@ fn trusted_types_create_policy_callback<'s>(
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if !trusted_types_factory_receiver_is_valid(scope, args.this()) {
let factory = args.this();
if !trusted_types_factory_receiver_is_valid(scope, factory) {
return;
}
let Some(name) = webidl::required_argument::<webidl::DomString>(
@@ -878,13 +882,21 @@ fn trusted_types_create_policy_callback<'s>(
return;
};
if !trusted_types_policy_name_allowed(scope, &name) {
let is_duplicate = trusted_types_policy_name_was_created(scope, factory, &name);
if !trusted_types_policy_name_allowed(scope, &name, is_duplicate) {
throw_type_error(
scope,
"Failed to execute 'createPolicy' on 'TrustedTypePolicyFactory': Content Security Policy disallows creating a policy with the given name.",
);
return;
}
if name == "default" && is_duplicate {
throw_type_error(
scope,
"Failed to execute 'createPolicy' on 'TrustedTypePolicyFactory': Policy with name \"default\" already exists.",
);
return;
}
let policy_name = v8_string(scope, &name).unwrap_or_else(|| v8::String::empty(scope));
let policy = TrustedTypePolicyObjectDeclaration {
@@ -905,17 +917,62 @@ fn trusted_types_create_policy_callback<'s>(
policy.into(),
);
}
trusted_types_record_created_policy_name(scope, factory, &name, is_duplicate);
rv.set(policy.into());
}
fn trusted_types_policy_name_allowed(scope: &mut v8::PinScope<'_, '_>, name: &str) -> bool {
if let Some(allowed) = crate::worker::worker_allows_trusted_type_policy_name(scope, name) {
fn trusted_types_policy_name_was_created<'s>(
scope: &mut v8::PinScope<'s, '_>,
factory: v8::Local<'s, v8::Object>,
name: &str,
) -> bool {
let Some(names) = get_private_value(scope, factory, TRUSTED_TYPES_CREATED_POLICY_NAMES_SLOT)
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
else {
return false;
};
(0..names.length()).any(|index| {
names
.get_index(scope, index)
.and_then(|value| v8::Local::<v8::String>::try_from(value).ok())
.is_some_and(|value| value.to_rust_string_lossy(scope) == name)
})
}
fn trusted_types_record_created_policy_name<'s>(
scope: &mut v8::PinScope<'s, '_>,
factory: v8::Local<'s, v8::Object>,
name: &str,
was_duplicate: bool,
) {
if was_duplicate {
return;
}
let Some(names) = get_private_value(scope, factory, TRUSTED_TYPES_CREATED_POLICY_NAMES_SLOT)
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
else {
return;
};
let Some(name) = v8_string(scope, name) else {
return;
};
let _ = names.set_index(scope, names.length(), name.into());
}
fn trusted_types_policy_name_allowed(
scope: &mut v8::PinScope<'_, '_>,
name: &str,
is_duplicate: bool,
) -> bool {
if let Some(allowed) =
crate::worker::worker_allows_trusted_type_policy_name_by_csp(scope, name, is_duplicate)
{
return allowed;
}
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
return true;
};
unsafe { &*host_ptr }.allows_trusted_type_policy_name(scope, name)
unsafe { &mut *host_ptr }.allows_trusted_type_policy_name_by_csp(scope, name, is_duplicate)
}
fn trusted_type_policy_name_getter_callback<'s>(
@@ -207,6 +207,7 @@ fn build_and_cache_trusted_types_state<'s>(
let factory = TrustedTypesFactoryObjectDeclaration {
empty_html,
empty_script,
created_policy_names: v8::Array::new(scope, 0),
}
.bind(scope)
.map_err(|error| anyhow!("failed to bind TrustedTypePolicyFactory object: {error}"))?;
@@ -6,7 +6,6 @@ use crate::content_security_policy::{
ContentSecurityPolicyResourceKind, ContentSecurityPolicyScriptElementRequest,
ContentSecurityPolicyStyleElementRequest, ContentSecurityPolicyUrlViolation,
ContentSecurityPolicyViolationEventFields, TrustedTypesForScriptRequirements,
content_security_policy_allows_trusted_type_policy_name,
content_security_policy_allows_trusted_types_eval,
content_security_policy_frame_ancestors_violation_with_disposition_and_reporting_endpoints,
content_security_policy_headers,
@@ -18,6 +17,7 @@ use crate::content_security_policy::{
content_security_policy_requires_trusted_types_for_script,
content_security_policy_script_element_url_violation_with_redirect_status_disposition_reporting_endpoints_and_request,
content_security_policy_style_element_url_violation_with_redirect_status_disposition_reporting_endpoints_and_request,
content_security_policy_trusted_types_policy_violation_with_disposition_and_reporting_endpoints,
content_security_policy_trusted_types_sink_violation_with_disposition_and_reporting_endpoints,
content_security_policy_url_violation_with_redirect_status_disposition_and_reporting_endpoints,
create_security_policy_violation_event,
@@ -1450,19 +1450,43 @@ impl DocumentRuntime {
document_policies_allow_trusted_types_eval(&policies)
}
pub(crate) fn allows_trusted_type_policy_name_for_document(
#[allow(clippy::too_many_arguments)]
pub(crate) fn trusted_type_policy_name_csp_check_for_document(
&self,
document_handle: Option<DomHandle>,
document_url: &Url,
response_policies: &[String],
response_report_only_policies: &[String],
response_reporting_endpoints: &ContentSecurityPolicyReportingEndpoints,
policy_name: &str,
) -> bool {
let policies = self.document_content_security_policy_strings_for_optional_document(
document_handle,
response_policies,
is_duplicate: bool,
) -> DocumentContentSecurityPolicyCheck {
let enforced_policies = self
.document_content_security_policy_strings_for_optional_document(
document_handle,
response_policies,
response_reporting_endpoints,
);
let report_only_policies = document_response_content_security_policy_strings(
response_report_only_policies,
response_reporting_endpoints,
);
document_policies_allow_trusted_type_policy_name(policies, policy_name)
DocumentContentSecurityPolicyCheck {
report_only_violation: document_trusted_types_policy_violation_from_document_policies(
report_only_policies,
document_url,
policy_name,
is_duplicate,
ContentSecurityPolicyDisposition::Report,
),
enforced_violation: document_trusted_types_policy_violation_from_document_policies(
enforced_policies,
document_url,
policy_name,
is_duplicate,
ContentSecurityPolicyDisposition::Enforce,
),
}
}
pub(crate) fn queue_content_security_policy_violation_event_best_effort<'s>(
@@ -2063,12 +2087,26 @@ fn iter_document_trusted_types_sink_policy_violations<'a>(
})
}
fn document_policies_allow_trusted_type_policy_name(
fn document_trusted_types_policy_violation_from_document_policies(
policies: Vec<DocumentContentSecurityPolicyString>,
document_url: &Url,
policy_name: &str,
) -> bool {
policies.into_iter().all(|policy| {
content_security_policy_allows_trusted_type_policy_name(&[policy.policy], policy_name)
is_duplicate: bool,
disposition: ContentSecurityPolicyDisposition,
) -> Option<DocumentContentSecurityPolicyViolation> {
policies.into_iter().find_map(|policy| {
let single_policy = [policy.policy.clone()];
let mut violation =
content_security_policy_trusted_types_policy_violation_with_disposition_and_reporting_endpoints(
&single_policy,
document_url,
policy_name,
is_duplicate,
disposition,
&policy.reporting_endpoints,
)?;
apply_document_policy_reporting_flags(&mut violation, &policy);
Some(violation)
})
}
@@ -868,25 +868,63 @@ impl JsContextHost {
.1
}
pub(crate) fn allows_trusted_type_policy_name(
&self,
scope: &mut v8::PinScope<'_, '_>,
pub(crate) fn allows_trusted_type_policy_name_by_csp<'s>(
&mut self,
scope: &mut v8::PinScope<'s, '_>,
policy_name: &str,
is_duplicate: bool,
) -> bool {
let Some(snapshot) =
self.owner_document_policy_snapshot(policy_owner_dispatch_scope(scope))
else {
let owner = policy_owner_dispatch_scope(scope);
let Some(snapshot) = self.owner_document_policy_snapshot(owner) else {
// A context between documents has no policy owner to report
// against; applying another document's CSP would enforce the
// wrong policy.
return true;
};
// SAFETY: JsContextHost is owned by the ScriptVm that owns this DocumentRuntime.
unsafe { &*self.runtime }.allows_trusted_type_policy_name_for_document(
let check = unsafe { &*self.runtime }.trusted_type_policy_name_csp_check_for_document(
snapshot.document_handle,
&snapshot.document_url,
&snapshot.policy_container.response_content_security_policies,
&snapshot
.policy_container
.response_content_security_report_only_policies,
&snapshot
.policy_container
.content_security_reporting_endpoints,
policy_name,
)
is_duplicate,
);
let (mut report_only_violation, mut enforced_violation) = check.into_violations();
if !self.active_inspector_dispatch
&& let Some((source_file, line_number, column_number)) =
current_script_violation_location(scope)
{
for violation in [&mut enforced_violation, &mut report_only_violation]
.into_iter()
.flatten()
{
violation.source_file.clone_from(&source_file);
violation.line_number = line_number;
violation.column_number = column_number;
}
}
let host_ptr: *mut JsContextHost = self;
let allowed = enforced_violation.is_none();
// Policy creation reports expose CSP list ordering. Response policy
// state is partitioned by disposition, with enforce policies modeled
// before report-only policies, so preserve that order here.
if let Some(violation) = enforced_violation {
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, &violation,
);
}
if let Some(violation) = report_only_violation {
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, &violation,
);
}
allowed
}
pub(crate) fn requires_trusted_types_for_script(
@@ -1010,26 +1048,8 @@ impl JsContextHost {
capture_current_script_location: bool,
) {
let source_location = (capture_current_script_location && !self.active_inspector_dispatch)
.then(|| v8::StackTrace::current_stack_trace(scope, 1))
.flatten()
.and_then(|stack| stack.get_frame(scope, 0))
.map(|frame| {
let source_file = frame
.get_script_name_or_source_url(scope)
.map(|source| source.to_rust_string_lossy(scope))
.map(|source| {
crate::content_security_policy::content_security_policy_source_file_for_report(
&source,
)
})
.unwrap_or_default();
let line_number = i32::try_from(frame.get_line_number())
.unwrap_or_default()
.max(0);
let column_number =
i32::try_from(frame.get_column()).unwrap_or_default().max(0);
(source_file, line_number, column_number)
});
.then(|| current_script_violation_location(scope))
.flatten();
let owner = policy_owner_dispatch_scope(scope);
for mut violation in self.trusted_types_sink_csp_violations_for_owner(owner, sink, sample) {
if let Some((source_file, line_number, column_number)) = &source_location {
@@ -1275,3 +1295,22 @@ fn current_script_call_location(scope: &v8::PinScope<'_, '_>) -> (i32, i32) {
let column = i32::try_from(frame.get_column()).unwrap_or(0).max(0);
(line, column)
}
fn current_script_violation_location(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<(String, i32, i32)> {
let stack = v8::StackTrace::current_stack_trace(scope, 1)?;
let frame = stack.get_frame(scope, 0)?;
let source_file = frame
.get_script_name_or_source_url(scope)
.map(|source| source.to_rust_string_lossy(scope))
.map(|source| {
crate::content_security_policy::content_security_policy_source_file_for_report(&source)
})
.unwrap_or_default();
let line_number = i32::try_from(frame.get_line_number())
.unwrap_or_default()
.max(0);
let column_number = i32::try_from(frame.get_column()).unwrap_or_default().max(0);
Some((source_file, line_number, column_number))
}
@@ -216,6 +216,72 @@ fn trusted_type_factory_default_policy_getter_tracks_the_branded_policy() {
);
}
#[test]
fn trusted_type_policy_creation_reports_name_and_duplicate_csp_violations() {
let mut vm = new_storage_test_vm("https://trusted-type-policy-csp.test/");
vm.set_response_content_security_policies(&[
"trusted-types allowed reportOnly duplicate".to_owned()
]);
vm.set_response_content_security_report_only_policies(&[
"trusted-types allowed duplicate".to_owned()
]);
let result = vm
.eval(
r#"
(() => {
const violations = [];
document.addEventListener("securitypolicyviolation", event => {
violations.push({
blockedURI: event.blockedURI,
effectiveDirective: event.effectiveDirective,
originalPolicy: event.originalPolicy,
disposition: event.disposition,
sample: event.sample
});
});
globalThis.__trustedTypePolicyCspViolations = violations;
const errorName = callback => {
try {
callback();
return "none";
} catch (error) {
return error.name;
}
};
const allowed = trustedTypes.createPolicy("allowed", {});
const reportOnly = trustedTypes.createPolicy("reportOnly", {});
const duplicate = trustedTypes.createPolicy("duplicate", {});
const duplicateError = errorName(() => trustedTypes.createPolicy("duplicate", {}));
const blockedError = errorName(() => trustedTypes.createPolicy("blocked", {}));
return JSON.stringify({
names: [allowed.name, reportOnly.name, duplicate.name],
duplicateError,
blockedError,
violations
});
})()
"#,
)
.expect("TrustedTypePolicy CSP creation probe should evaluate");
assert_eq!(
result,
r#"{"names":["allowed","reportOnly","duplicate"],"duplicateError":"TypeError","blockedError":"TypeError","violations":[]}"#
);
assert_eq!(
drain_pre_domcontentloaded_non_script_page_tasks_for_test(&mut vm),
5
);
assert_eq!(
vm.eval("JSON.stringify(globalThis.__trustedTypePolicyCspViolations)")
.expect("queued TrustedTypePolicy CSP violations should be observable"),
r#"[{"blockedURI":"trusted-types-policy","effectiveDirective":"trusted-types","originalPolicy":"trusted-types allowed duplicate","disposition":"report","sample":"reportOnly"},{"blockedURI":"trusted-types-policy","effectiveDirective":"trusted-types","originalPolicy":"trusted-types allowed reportOnly duplicate","disposition":"enforce","sample":"duplicate"},{"blockedURI":"trusted-types-policy","effectiveDirective":"trusted-types","originalPolicy":"trusted-types allowed duplicate","disposition":"report","sample":"duplicate"},{"blockedURI":"trusted-types-policy","effectiveDirective":"trusted-types","originalPolicy":"trusted-types allowed reportOnly duplicate","disposition":"enforce","sample":"blocked"},{"blockedURI":"trusted-types-policy","effectiveDirective":"trusted-types","originalPolicy":"trusted-types allowed duplicate","disposition":"report","sample":"blocked"}]"#
);
}
#[test]
fn element_markup_sinks_enforce_trusted_html_and_standard_sink_names() {
let mut vm = new_storage_test_vm("https://element-markup-trusted-types.test/");
@@ -8,6 +8,7 @@ use crate::content_security_policy::{
ContentSecurityPolicyDisposition, ContentSecurityPolicyRedirectStatus,
ContentSecurityPolicyResourceKind, ContentSecurityPolicyUrlViolation,
ContentSecurityPolicyViolationEventFields, content_security_policy_report_requests,
content_security_policy_trusted_types_policy_violation_with_disposition_and_reporting_endpoints,
content_security_policy_trusted_types_sink_violation_with_disposition_and_reporting_endpoints,
content_security_policy_url_violation_for_checked_url_with_redirect_status_disposition_and_reporting_endpoints,
content_security_policy_url_violation_with_redirect_status_disposition_and_reporting_endpoints,
@@ -107,6 +108,49 @@ pub(super) fn dispatch_worker_trusted_types_sink_violation_event_for_state<'s>(
}
}
pub(super) fn allows_worker_trusted_type_policy_name_for_state<'s>(
scope: &mut v8::PinScope<'s, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
policy_name: &str,
is_duplicate: bool,
) -> bool {
let (report_only_violation, enforced_violation) = {
let state_ref = state.borrow();
let Some(protected_url) = state_ref.current_script_url.as_ref() else {
return true;
};
let report_only_violation =
content_security_policy_trusted_types_policy_violation_with_disposition_and_reporting_endpoints(
&state_ref.content_security_report_only_policies,
protected_url,
policy_name,
is_duplicate,
ContentSecurityPolicyDisposition::Report,
&state_ref.content_security_reporting_endpoints,
);
let enforced_violation =
content_security_policy_trusted_types_policy_violation_with_disposition_and_reporting_endpoints(
&state_ref.content_security_policies,
protected_url,
policy_name,
is_duplicate,
ContentSecurityPolicyDisposition::Enforce,
&state_ref.content_security_reporting_endpoints,
);
(report_only_violation, enforced_violation)
};
let allowed = enforced_violation.is_none();
// Match window policy creation reporting: enforce response policies are
// modeled before report-only policies in the partitioned policy state.
if let Some(violation) = enforced_violation {
dispatch_worker_content_security_policy_violation_event_for_state(scope, state, &violation);
}
if let Some(violation) = report_only_violation {
dispatch_worker_content_security_policy_violation_event_for_state(scope, state, &violation);
}
allowed
}
fn create_worker_content_security_policy_violation_event<'s>(
scope: &mut v8::PinScope<'s, '_>,
violation: &ContentSecurityPolicyUrlViolation,
@@ -6160,14 +6160,18 @@ pub(crate) fn worker_current_script_url(scope: &mut v8::PinScope<'_, '_>) -> Opt
get_worker_state(scope)?.borrow().current_script_url.clone()
}
pub(crate) fn worker_allows_trusted_type_policy_name(
pub(crate) fn worker_allows_trusted_type_policy_name_by_csp(
scope: &mut v8::PinScope<'_, '_>,
name: &str,
is_duplicate: bool,
) -> Option<bool> {
let state = get_worker_state(scope)?;
Some(
crate::content_security_policy::content_security_policy_allows_trusted_type_policy_name(
&get_worker_state(scope)?.borrow().content_security_policies,
content_security_policy::allows_worker_trusted_type_policy_name_for_state(
scope,
&state,
name,
is_duplicate,
),
)
}
+1 -1
View File
@@ -37,7 +37,7 @@ pub(crate) use global_scope::{
remove_worker_message_port_event_listener_by_id, reserve_nested_worker_context,
send_worker_websocket_binary, send_worker_websocket_text, service_worker_runtime_identity,
try_worker_xhr_abort_callback, try_worker_xhr_reschedule_timeout_after_timeout_change,
try_worker_xhr_send_callback, worker_allows_trusted_type_policy_name,
try_worker_xhr_send_callback, worker_allows_trusted_type_policy_name_by_csp,
worker_allows_trusted_types_eval, worker_broadcast_channel_registry,
worker_broadcast_channel_storage_key, worker_broadcast_channel_wake_sender,
worker_broadcast_channel_wrapper, worker_current_script_url, worker_global_is_closed,