feat(controls): B1 — custom semgrep detectors for 4 CRA controls (#218)
This commit was merged in pull request #218.
This commit is contained in:
+69
-3
@@ -66,6 +66,14 @@ pub struct ControlMap {
|
||||
|
||||
const CRA_MAP_JSON: &str = include_str!("../data/cra_control_map.json");
|
||||
|
||||
/// Whether an authored rule id `bound` matches a scanner's emitted rule id
|
||||
/// `actual`. semgrep prefixes local-rule check_ids with a path
|
||||
/// (`tmp.compliance-cra-semgrep.cra-ai-1-flask-debug-enabled`), so match the final
|
||||
/// id segment rather than requiring exact equality.
|
||||
fn rule_id_matches(bound: &str, actual: &str) -> bool {
|
||||
actual == bound || actual.ends_with(&format!(".{bound}"))
|
||||
}
|
||||
|
||||
impl ControlMap {
|
||||
/// Load the built-in CRA control map (the embedded, authored LUT).
|
||||
pub fn cra() -> Result<Self, MapError> {
|
||||
@@ -80,12 +88,28 @@ impl ControlMap {
|
||||
/// Controls whose bindings include the given `tool` + `cwe` — used to attach a
|
||||
/// raw tool finding back to the control(s) it's evidence for.
|
||||
pub fn controls_for(&self, tool: &str, cwe: &str) -> Vec<&ControlEntry> {
|
||||
self.controls_for_finding(tool, Some(cwe), None)
|
||||
}
|
||||
|
||||
/// Controls a tool finding is evidence for, matched by CWE and/or the specific
|
||||
/// rule id that fired. Off-the-shelf findings bind by CWE; our custom detectors
|
||||
/// bind by rule id (precise — a broad CWE would over-attribute and then the
|
||||
/// grounded judge could drop a genuine finding as a control false positive).
|
||||
pub fn controls_for_finding(
|
||||
&self,
|
||||
tool: &str,
|
||||
cwe: Option<&str>,
|
||||
rule_id: Option<&str>,
|
||||
) -> Vec<&ControlEntry> {
|
||||
self.controls
|
||||
.iter()
|
||||
.filter(|c| {
|
||||
c.scans
|
||||
.iter()
|
||||
.any(|s| s.tool == tool && s.cwe.iter().any(|w| w == cwe))
|
||||
c.scans.iter().any(|s| {
|
||||
s.tool == tool
|
||||
&& (cwe.is_some_and(|w| s.cwe.iter().any(|x| x == w))
|
||||
|| rule_id
|
||||
.is_some_and(|r| s.rules.iter().any(|b| rule_id_matches(b, r))))
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -160,4 +184,46 @@ mod tests {
|
||||
assert!(s.needs_tooling > 0);
|
||||
assert!(s.not_code_checkable > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rule_id_matching_handles_semgrep_path_prefix() {
|
||||
let bound = "cra-ai-1-flask-debug-enabled";
|
||||
assert!(rule_id_matches(bound, bound)); // exact
|
||||
assert!(rule_id_matches(
|
||||
bound,
|
||||
"tmp.compliance-cra-semgrep.cra-ai-1-flask-debug-enabled"
|
||||
)); // semgrep path prefix
|
||||
assert!(!rule_id_matches(
|
||||
bound,
|
||||
"cra-ai-1-flask-debug-enabled-extra"
|
||||
)); // not a suffix segment
|
||||
assert!(!rule_id_matches(
|
||||
bound,
|
||||
"python.lang.security.exec-detected"
|
||||
)); // unrelated
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_rule_finding_attaches_to_control_by_rule_id() {
|
||||
let map = ControlMap::cra().unwrap();
|
||||
// cra-ai-1 is now tool-covered by custom rules.
|
||||
assert_eq!(map.coverage("cra-ai-1").unwrap().status, Coverage::Covered);
|
||||
// A prefixed check_id still maps back to cra-ai-1 by rule id.
|
||||
let hits =
|
||||
map.controls_for_finding("semgrep", None, Some("tmp.x.cra-ai-1-tls-verify-disabled"));
|
||||
assert!(hits.iter().any(|c| c.control == "cra-ai-1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_rule_controls_do_not_bind_by_broad_cwe() {
|
||||
let map = ControlMap::cra().unwrap();
|
||||
// cra-ai-1 rules emit CWE-489 in metadata, but the LUT binds by rule id
|
||||
// only (cwe: []) — so a stray CWE-489 finding must NOT attach to it.
|
||||
assert!(map.controls_for("semgrep", "CWE-489").is_empty());
|
||||
// The CWE path for off-the-shelf findings is unchanged.
|
||||
assert!(map
|
||||
.controls_for("semgrep", "CWE-798")
|
||||
.iter()
|
||||
.any(|c| c.control == "cra-ai-8"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user