mirror of
https://github.com/lexmount/moli.git
synced 2026-10-06 16:01:00 +00:00
fix(csp): validate Trusted Types directive name tokens
Discard non-ASCII directives independently before duplicate lookup and retain exact ASCII whitespace boundaries. Match literal Trusted Types allowlist entries only when they satisfy tt-policy-name, while preserving arbitrary policy names without CSP or under a wildcard. Add exhaustive ASCII token tests, directive parsing regressions, and enforce/report-only delivery coverage. Validated with fmt, full-workspace Clippy and all 17,735 nextest tests. The 219-case Trusted Types suite gains one passing case and five passing subtests in both CLI and CDP, with all other semantic results unchanged.
This commit is contained in:
@@ -3280,7 +3280,6 @@ svg/types/scripted/SVGLength-viewport.html
|
||||
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/should-trusted-type-policy-creation-be-blocked-by-csp-002.html
|
||||
trusted-types/trusted-types-createHTMLDocument.html
|
||||
trusted-types/trusted-types-report-only.html
|
||||
trusted-types/trusted-types-reporting-for-DedicatedWorker-ServiceWorkerContainer-register.https.html
|
||||
|
||||
@@ -8437,6 +8437,7 @@ trusted-types/should-sink-type-mismatch-violation-be-blocked-by-csp-001.html
|
||||
trusted-types/should-sink-type-mismatch-violation-be-blocked-by-csp-002-worker.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-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
|
||||
|
||||
@@ -1199,6 +1199,9 @@ fn policy_allows_trusted_type_policy_name(
|
||||
source == "*"
|
||||
|| (!source.is_empty()
|
||||
&& source == policy_name
|
||||
// Only tt-policy-name tokens can whitelist a literal name.
|
||||
// The policy API itself accepts arbitrary strings, including
|
||||
// under a wildcard, so do not validate policy_name globally.
|
||||
&& source.bytes().all(|byte| {
|
||||
byte.is_ascii_alphanumeric()
|
||||
|| matches!(byte, b'-' | b'#' | b'=' | b'_' | b'/' | b'@' | b'.' | b'%')
|
||||
@@ -1207,7 +1210,7 @@ fn policy_allows_trusted_type_policy_name(
|
||||
let duplicate_is_allowed = !is_duplicate
|
||||
|| sources
|
||||
.iter()
|
||||
.any(|source| csp_keyword_eq(source.trim(), "allow-duplicates"));
|
||||
.any(|source| csp_keyword_eq(source, "allow-duplicates"));
|
||||
name_is_allowed && duplicate_is_allowed
|
||||
}
|
||||
|
||||
@@ -3477,26 +3480,31 @@ mod tests {
|
||||
assert!(policy_allows_trusted_type_policy_name(
|
||||
&format!("trusted-types {name}"),
|
||||
name,
|
||||
false
|
||||
false,
|
||||
));
|
||||
}
|
||||
assert!(policy_allows_trusted_type_policy_name(
|
||||
"trusted-types valid policy*name",
|
||||
"valid",
|
||||
false
|
||||
false,
|
||||
));
|
||||
assert!(!policy_allows_trusted_type_policy_name(
|
||||
"trusted-types valid policy*name",
|
||||
"policy*name",
|
||||
false
|
||||
false,
|
||||
));
|
||||
for wildcard in ["\u{000b}*", "*\u{000b}", "policy*"] {
|
||||
assert!(!policy_allows_trusted_type_policy_name(
|
||||
&format!("trusted-types {wildcard}"),
|
||||
"valid",
|
||||
false
|
||||
false,
|
||||
));
|
||||
}
|
||||
assert!(!policy_allows_trusted_type_policy_name(
|
||||
"trusted-types valid \u{000b}'allow-duplicates'",
|
||||
"valid",
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3512,6 +3520,16 @@ mod tests {
|
||||
for policy in ["", "trusted-types *"] {
|
||||
assert!(policy_allows_trusted_type_policy_name(policy, name, false));
|
||||
}
|
||||
assert!(!policy_allows_trusted_type_policy_name(
|
||||
"trusted-types *",
|
||||
name,
|
||||
true,
|
||||
));
|
||||
assert!(policy_allows_trusted_type_policy_name(
|
||||
"trusted-types * 'allow-duplicates'",
|
||||
name,
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -433,6 +433,80 @@ fn trusted_type_policy_creation_reports_name_and_duplicate_csp_violations() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_types_csp_name_grammar_preserves_enforcement_and_report_only_delivery() {
|
||||
let policy = "trusted-types valid policy*name policy$name policy?name policy!name";
|
||||
let names = [
|
||||
"valid",
|
||||
"policy*name",
|
||||
"policy$name",
|
||||
"policy?name",
|
||||
"policy!name",
|
||||
"política",
|
||||
"",
|
||||
];
|
||||
for enforce in [false, true] {
|
||||
let mut vm = new_storage_test_vm("https://trusted-types-name-grammar.test/");
|
||||
if enforce {
|
||||
vm.set_response_content_security_policies(&[
|
||||
policy.to_owned(),
|
||||
"trusted-types ignored política".to_owned(),
|
||||
]);
|
||||
}
|
||||
vm.set_response_content_security_report_only_policies(&[
|
||||
policy.to_owned(),
|
||||
"trusted-types ignored política".to_owned(),
|
||||
]);
|
||||
let actual = vm.eval(r#"
|
||||
globalThis.policyNameGrammarReports = [];
|
||||
document.addEventListener('securitypolicyviolation', event => {
|
||||
policyNameGrammarReports.push([event.sample, event.originalPolicy, event.disposition]);
|
||||
});
|
||||
JSON.stringify(['valid', 'policy*name', 'policy$name', 'policy?name', 'policy!name', 'política', ''].map(name => {
|
||||
try { return trustedTypes.createPolicy(name).name; }
|
||||
catch (error) { return error instanceof TypeError ? 'TypeError' : error.name; }
|
||||
}));
|
||||
"#).unwrap();
|
||||
assert_eq!(
|
||||
serde_json::from_str::<serde_json::Value>(&actual).unwrap(),
|
||||
if enforce {
|
||||
serde_json::json!([
|
||||
"valid",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError",
|
||||
"TypeError"
|
||||
])
|
||||
} else {
|
||||
serde_json::json!(names)
|
||||
},
|
||||
);
|
||||
assert_eq!(vm.eval("policyNameGrammarReports.length").unwrap(), "0");
|
||||
let expected = names[1..]
|
||||
.iter()
|
||||
.flat_map(|name| {
|
||||
let mut reports = Vec::new();
|
||||
if enforce {
|
||||
reports.push(serde_json::json!([name, policy, "enforce"]));
|
||||
}
|
||||
reports.push(serde_json::json!([name, policy, "report"]));
|
||||
reports
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
drain_pre_domcontentloaded_non_script_page_tasks_for_test(&mut vm),
|
||||
expected.len(),
|
||||
);
|
||||
let actual = vm.eval("JSON.stringify(policyNameGrammarReports)").unwrap();
|
||||
assert_eq!(
|
||||
serde_json::from_str::<serde_json::Value>(&actual).unwrap(),
|
||||
serde_json::json!(expected),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_type_policy_creation_reports_every_violated_policy() {
|
||||
let enforced = [
|
||||
|
||||
Reference in New Issue
Block a user