From 7e0553ed26f2cebe015401ee268932bdc0be151f Mon Sep 17 00:00:00 2001 From: ldm0 Date: Wed, 26 Aug 2026 17:08:27 +0800 Subject: [PATCH] refactor(csp): unify navigation embedding checks --- moli-renderer-v8/src/document_runtime.rs | 5 +- .../src/document_runtime/security_policy.rs | 82 ++++++++++++++++++- .../context_host/child_documents/loads.rs | 31 ++----- moli-renderer-v8/src/runtime/page_vm/mod.rs | 9 ++ 4 files changed, 101 insertions(+), 26 deletions(-) diff --git a/moli-renderer-v8/src/document_runtime.rs b/moli-renderer-v8/src/document_runtime.rs index 3f52c2cc1d..e1843478b4 100644 --- a/moli-renderer-v8/src/document_runtime.rs +++ b/moli-renderer-v8/src/document_runtime.rs @@ -84,8 +84,9 @@ pub(crate) use script_lifecycle::{ }; pub(crate) use security_policy::{ DocumentConnectPolicySnapshot, DocumentContentSecurityPolicyCheck, - DocumentContentSecurityPolicyViolation, DocumentSubresourceCspKind, - create_content_security_policy_violation_event, document_content_security_policy_error_message, + DocumentContentSecurityPolicyViolation, DocumentNavigationEmbeddingContext, + DocumentSubresourceCspKind, create_content_security_policy_violation_event, + document_content_security_policy_error_message, }; pub(crate) use stylesheet_runtime::attribute_reprocesses_connected_stylesheet; #[cfg(test)] diff --git a/moli-renderer-v8/src/document_runtime/security_policy.rs b/moli-renderer-v8/src/document_runtime/security_policy.rs index 27b9910157..616d6624cd 100644 --- a/moli-renderer-v8/src/document_runtime/security_policy.rs +++ b/moli-renderer-v8/src/document_runtime/security_policy.rs @@ -7,7 +7,9 @@ use crate::content_security_policy::{ ContentSecurityPolicyStyleElementRequest, ContentSecurityPolicyUrlViolation, ContentSecurityPolicyViolationEventFields, TrustedTypesForScriptRequirements, content_security_policy_allows_trusted_type_policy_name, - content_security_policy_allows_trusted_types_eval, content_security_policy_headers, + content_security_policy_allows_trusted_types_eval, + content_security_policy_frame_ancestors_violation_with_disposition_and_reporting_endpoints, + content_security_policy_headers, content_security_policy_inline_script_element_violation_with_disposition_and_reporting_endpoints, content_security_policy_inline_source_violation_with_disposition_and_reporting_endpoints, content_security_policy_inline_style_element_violation_with_disposition_and_reporting_endpoints, @@ -48,6 +50,10 @@ pub(crate) struct DocumentContentSecurityPolicyCheck { } impl DocumentContentSecurityPolicyCheck { + pub(crate) fn has_no_violations(&self) -> bool { + self.report_only_violation.is_none() && self.enforced_violation.is_none() + } + pub(crate) fn into_violations( self, ) -> ( @@ -68,6 +74,12 @@ impl DocumentContentSecurityPolicyCheck { } } +#[derive(Clone, Copy, Debug)] +pub(crate) enum DocumentNavigationEmbeddingContext<'a> { + TopLevel, + Nested(&'a [Option]), +} + #[derive(Debug, Clone)] pub(crate) struct DocumentConnectPolicySnapshot { enforce_policies: Vec, @@ -154,6 +166,37 @@ impl DocumentPolicyContainer { } } + pub(crate) fn navigation_response_frame_ancestors_check( + &self, + protected_url: &Url, + embedding_context: DocumentNavigationEmbeddingContext<'_>, + ) -> DocumentContentSecurityPolicyCheck { + let DocumentNavigationEmbeddingContext::Nested(ancestor_origins) = embedding_context else { + return DocumentContentSecurityPolicyCheck { + report_only_violation: None, + enforced_violation: None, + }; + }; + DocumentContentSecurityPolicyCheck { + report_only_violation: + content_security_policy_frame_ancestors_violation_with_disposition_and_reporting_endpoints( + &self.response_content_security_report_only_policies, + protected_url, + ancestor_origins, + ContentSecurityPolicyDisposition::Report, + &self.content_security_reporting_endpoints, + ), + enforced_violation: + content_security_policy_frame_ancestors_violation_with_disposition_and_reporting_endpoints( + &self.response_content_security_policies, + protected_url, + ancestor_origins, + ContentSecurityPolicyDisposition::Enforce, + &self.content_security_reporting_endpoints, + ), + } + } + pub(crate) fn clear_content_security_policy_for_bypass(&mut self) { self.document_content_security_policies.clear(); self.response_content_security_policies.clear(); @@ -3083,4 +3126,41 @@ mod tests { Some("https://example.test/reports/csp") ); } + + #[test] + fn navigation_response_frame_ancestors_check_distinguishes_top_level_and_nested() { + let protected_url = Url::parse("https://child.test/frame.html").unwrap(); + let policy = DocumentPolicyContainer { + response_content_security_policies: vec!["frame-ancestors 'self'".to_owned()], + response_content_security_report_only_policies: vec![ + "frame-ancestors https://reported.test".to_owned(), + ], + ..Default::default() + }; + + assert!( + policy + .navigation_response_frame_ancestors_check( + &protected_url, + DocumentNavigationEmbeddingContext::TopLevel, + ) + .has_no_violations() + ); + + let ancestors = [Some(Url::parse("https://embedder.test").unwrap())]; + let (report_only, enforced) = policy + .navigation_response_frame_ancestors_check( + &protected_url, + DocumentNavigationEmbeddingContext::Nested(&ancestors), + ) + .into_violations(); + assert_eq!( + report_only.unwrap().disposition, + ContentSecurityPolicyDisposition::Report + ); + assert_eq!( + enforced.unwrap().disposition, + ContentSecurityPolicyDisposition::Enforce + ); + } } diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs b/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs index 58ad2a869a..93ac98c57c 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs @@ -8,11 +8,9 @@ use super::{ use crate::referrer_policy::response_referrer_policy_from_headers; use crate::{ content_security_policy::{ - ContentSecurityPolicyDisposition, ContentSecurityPolicyViolationEventFields, - content_security_policy_frame_ancestors_violation_with_disposition_and_reporting_endpoints, - send_content_security_policy_reports, + ContentSecurityPolicyViolationEventFields, send_content_security_policy_reports, }, - document_runtime::{DocumentPolicyContainer, DomHandle}, + document_runtime::{DocumentNavigationEmbeddingContext, DocumentPolicyContainer, DomHandle}, document_script_scheduler::FrameDocumentClassicScriptSchedulerWork, frame_owner_model::{ ChildDocumentNavigationFetchTarget, DocumentCreationKind, @@ -411,26 +409,13 @@ impl JsContextHost { .clear_content_security_policy_for_bypass(); } let ancestor_origins = self.child_document_frame_ancestor_origins(handle); - let reporting_endpoints = - &loaded.policy_container.content_security_reporting_endpoints; - let report_only_violation = - content_security_policy_frame_ancestors_violation_with_disposition_and_reporting_endpoints( - &loaded - .policy_container - .response_content_security_report_only_policies, + let (report_only_violation, enforced_violation) = loaded + .policy_container + .navigation_response_frame_ancestors_check( &loaded.final_url, - &ancestor_origins, - ContentSecurityPolicyDisposition::Report, - reporting_endpoints, - ); - let enforced_violation = - content_security_policy_frame_ancestors_violation_with_disposition_and_reporting_endpoints( - &loaded.policy_container.response_content_security_policies, - &loaded.final_url, - &ancestor_origins, - ContentSecurityPolicyDisposition::Enforce, - reporting_endpoints, - ); + DocumentNavigationEmbeddingContext::Nested(&ancestor_origins), + ) + .into_violations(); for violation in report_only_violation .iter() .chain(enforced_violation.iter()) diff --git a/moli-renderer-v8/src/runtime/page_vm/mod.rs b/moli-renderer-v8/src/runtime/page_vm/mod.rs index f57688dca6..5c0f8acf1e 100644 --- a/moli-renderer-v8/src/runtime/page_vm/mod.rs +++ b/moli-renderer-v8/src/runtime/page_vm/mod.rs @@ -1108,6 +1108,15 @@ impl PageVmEnvConfig { headers, final_url, ) .with_content_security_policy_bypass(self.bypass_content_security_policy); + debug_assert!( + self.document_policy_container + .navigation_response_frame_ancestors_check( + final_url, + crate::document_runtime::DocumentNavigationEmbeddingContext::TopLevel, + ) + .has_no_violations(), + "a top-level navigation response cannot violate frame-ancestors" + ); self.document_default_language = crate::document_language::document_default_language_from_headers(headers); self.document_last_modified =