mirror of
https://github.com/lexmount/moli.git
synced 2026-10-04 16:00:47 +00:00
fix(trusted-types): enforce policy creation CSP
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user