refactor(csp): unify navigation embedding checks

This commit is contained in:
ldm0
2026-08-26 17:08:27 +08:00
parent c4aa203c77
commit 7e0553ed26
4 changed files with 101 additions and 26 deletions
+3 -2
View File
@@ -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)]
@@ -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<Url>]),
}
#[derive(Debug, Clone)]
pub(crate) struct DocumentConnectPolicySnapshot {
enforce_policies: Vec<String>,
@@ -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
);
}
}
@@ -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())
@@ -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 =