///|
fn push_if_missing(
policy : Policy,
findings : Array[Finding],
directive : String,
code : String,
message : String,
) -> Unit {
if !has_directive(policy, directive) {
findings.push(make_finding(Warning, code, directive, message, 0))
}
}
///|
fn push_if_source(
policy : Policy,
findings : Array[Finding],
directive_name : String,
source : String,
code : String,
message : String,
) -> Unit {
match get_directive(policy, directive_name) {
None => ()
Some(directive) =>
if directive_has_source(directive, source) {
findings.push(
make_finding(Warning, code, directive_name, message, directive.line),
)
}
}
}
///|
fn audit_required_directives(
policy : Policy,
findings : Array[Finding],
) -> Unit {
push_if_missing(
policy, findings, "default-src", "missing-default-src", "default-src should bound fallback fetches",
)
push_if_missing(
policy, findings, "object-src", "missing-object-src", "object-src 'none' is recommended",
)
push_if_missing(
policy, findings, "base-uri", "missing-base-uri", "base-uri should prevent injected base tags",
)
push_if_missing(
policy, findings, "frame-ancestors", "missing-frame-ancestors", "frame-ancestors should control embedding",
)
}
///|
fn audit_unsafe_sources(policy : Policy, findings : Array[Finding]) -> Unit {
push_if_source(
policy, findings, "script-src", "'unsafe-inline'", "script-unsafe-inline", "inline script should use nonce or hash instead",
)
push_if_source(
policy, findings, "script-src", "'unsafe-eval'", "script-unsafe-eval", "unsafe eval weakens script isolation",
)
push_if_source(
policy, findings, "style-src", "'unsafe-inline'", "style-unsafe-inline", "inline styles should be migrated to nonce or hash",
)
push_if_source(
policy, findings, "default-src", "*", "default-wildcard", "default-src wildcard allows broad fallback fetches",
)
}
///|
fn audit_none_misuse(policy : Policy, findings : Array[Finding]) -> Unit {
for directive in policy.directives {
if directive_has_source(directive, "'none'") &&
directive.sources.length() > 1 {
findings.push(
make_finding(
Warning,
"none-with-other-sources",
directive.name,
"'none' is ignored when combined with other source expressions",
directive.line,
),
)
}
}
}
///|
fn audit_plain_http(policy : Policy, findings : Array[Finding]) -> Unit {
for directive in policy.directives {
for source in directive.sources {
if starts_with(source.value, "http:") {
findings.push(
make_finding(
Warning,
"plain-http-source",
directive.name,
"plain http source should be replaced with https",
directive.line,
),
)
}
}
}
}
///|
fn audit_parser_diagnostics(policy : Policy, findings : Array[Finding]) -> Unit {
for finding in policy.diagnostics {
findings.push(finding)
}
}
///|
/// Run the built-in CSP audit rule set.
pub fn audit(policy : Policy) -> Array[Finding] {
let findings : Array[Finding] = []
audit_parser_diagnostics(policy, findings)
audit_required_directives(policy, findings)
audit_unsafe_sources(policy, findings)
audit_none_misuse(policy, findings)
audit_plain_http(policy, findings)
findings
}