{"record":{"id":"e97aa2d42e24bf15","repo":"zeroclaw-labs/zeroclaw","slug":"findings-cvss-score-must-be-between-0-0-and-10","errorCode":null,"errorMessage":"findings[{}].cvss_score must be between 0.0 and 10.0, got {}","messagePattern":"findings\\[(.+?)\\]\\.cvss_score must be between 0\\.0 and 10\\.0, got (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/security/vulnerability.rs","lineNumber":86,"sourceCode":"    }\n}\n\npub fn parse_vulnerability_json(json_str: &str) -> anyhow::Result<VulnerabilityReport> {\n    let report: VulnerabilityReport = serde_json::from_str(json_str).map_err(|e| {\n        ::zeroclaw_log::record!(\n            WARN,\n            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                .with_attrs(::serde_json::json!({\"error\": format!(\"{}\", e)})),\n            \"vulnerability report rejected: JSON parse failed\"\n        );\n        anyhow::Error::msg(format!(\"Failed to parse vulnerability report: {e}\"))\n    })?;\n\n    for (i, finding) in report.findings.iter().enumerate() {\n        if !(0.0..=10.0).contains(&finding.cvss_score) {\n            anyhow::bail!(\n                \"findings[{}].cvss_score must be between 0.0 and 10.0, got {}\",\n                i,\n                finding.cvss_score\n            );\n        }\n    }\n\n    Ok(report)\n}\n\n/// Generate a summary of the vulnerability report.\npub fn generate_summary(report: &VulnerabilityReport) -> String {\n    if report.findings.is_empty() {\n        return format!(\n            \"Vulnerability scan by {} on {}: No findings.\",\n            report.scanner,\n            report.scan_date.format(\"%Y-%m-%d\")\n        );\n    }","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/security/vulnerability.rs#L68-L104","documentation":"parse_vulnerability_json deserializes the report with serde, then enforces 0.0 <= findings[i].cvss_score <= 10.0 for every finding (vulnerability.rs:83-91). The index in the message identifies the offending finding. NaN also fails, because the range check is false for NaN.","triggerScenarios":"Feeding a report where a finding has cvss_score -1 (a common 'unknown' sentinel), 99, or NaN; the message prints the exact array index and value.","commonSituations":"Scanner exporters write -1 for un-scored CVEs; a CSV-to-JSON converter mangles numeric fields; hand-edited report JSON with placeholder scores.","solutions":["Sanitize the source report: clamp or drop findings with sentinel scores before calling parse_vulnerability_json","Fix the exporter so unknown scores are omitted or written as 0.0","Pre-validate the findings array yourself if you accept untrusted scanner output"],"exampleFix":"// before\nlet report = parse_vulnerability_json(&raw)?;\n\n// after: clamp sentinel scores before parsing\nlet mut value: serde_json::Value = serde_json::from_str(&raw)?;\nif let Some(findings) = value.get_mut(\"findings\").and_then(|f| f.as_array_mut()) {\n    for f in findings {\n        if let Some(score) = f.get(\"cvss_score\").and_then(|s| s.as_f64()) {\n            if !(0.0..=10.0).contains(&score) {\n                f[\"cvss_score\"] = serde_json::json!(score.clamp(0.0, 10.0));\n            }\n        }\n    }\n}\nlet report = parse_vulnerability_json(&value.to_string())?;","handlingStrategy":"validation","validationCode":"fn cvss_scores_valid(json: &str) -> Result<(), String> {\n    let v: serde_json::Value = serde_json::from_str(json).map_err(|e| e.to_string())?;\n    let findings = v.get(\"findings\").and_then(|f| f.as_array()).ok_or(\"missing findings\")?;\n    for (i, f) in findings.iter().enumerate() {\n        let ok = f.get(\"cvss_score\")\n            .and_then(|s| s.as_f64())\n            .map(|s| (0.0..=10.0).contains(&s))\n            .unwrap_or(false);\n        if !ok { return Err(format!(\"findings[{i}].cvss_score out of range\")); }\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"Catch the error and parse the 'findings[{i}]' index from the message to report the exact offending finding to the scanner operator; do not retry the same input.","preventionTips":["Normalize scanner output (clamp or drop sentinel scores) at ingest time","Add contract tests containing -1, 10.1, and NaN scores","Pin exporter versions known to emit valid CVSS v3 values"],"tags":["security","vulnerability","cvss","json-validation","rust"],"backgroundTag":"schema-validation-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}