secfinding 0.4.0

Universal security finding types for vulnerability scanners.
Documentation
// Extracted from src/bridge.rs
use secfinding::*;
use secfinding::bridge::*;

use super::*;

#[test]
fn severity_mapping_covers_all() {
    assert_eq!(map_severity(secir::Severity::Info), UniversalSeverity::Info);
    assert_eq!(map_severity(secir::Severity::Low), UniversalSeverity::Low);
    assert_eq!(
        map_severity(secir::Severity::Medium),
        UniversalSeverity::Medium
    );
    assert_eq!(map_severity(secir::Severity::High), UniversalSeverity::High);
    assert_eq!(
        map_severity(secir::Severity::Critical),
        UniversalSeverity::Critical
    );
}

#[test]
fn kind_mapping_covers_all_variants() {
    let variants = [
        secir::finding::FindingKind::Vulnerability,
        secir::finding::FindingKind::Misconfiguration,
        secir::finding::FindingKind::Exposure,
        secir::finding::FindingKind::TechDetect,
        secir::finding::FindingKind::DefaultCredentials,
        secir::finding::FindingKind::InfoDisclosure,
        secir::finding::FindingKind::FileDiscovery,
        secir::finding::FindingKind::Other,
    ];
    for v in &variants {
        let _ = map_kind(v);
    }
}

#[test]
fn basic_conversion() {
    let ir = secir::finding::Finding::new(
        "test-template".to_string(),
        "Test Template".to_string(),
        "https://example.com".to_string(),
        secir::Severity::High,
        "https://example.com/admin".to_string(),
    );

    let finding = to_universal(&ir, "calyx").unwrap();
    assert_eq!(finding.scanner(), "calyx");
    assert_eq!(finding.target(), "https://example.com");
    assert_eq!(finding.severity(), UniversalSeverity::High);
    assert_eq!(finding.title(), "Test Template");
    assert_eq!(finding.kind(), FindingKind::Other);
}

#[test]
fn conversion_preserves_evidence() {
    let mut ir = secir::finding::Finding::new(
        "xss-test".to_string(),
        "XSS Test".to_string(),
        "https://example.com".to_string(),
        secir::Severity::Medium,
        "https://example.com/search".to_string(),
    );
    ir.request = Some("GET /search?q=<script> HTTP/1.1".to_string());
    ir.response = Some("HTTP/1.1 200 OK\n\n<script>".to_string());
    ir.matched_values = vec!["<script>".to_string()];
    ir.curl_command = Some("curl 'https://example.com/search?q=<script>'".to_string());

    let finding = to_universal(&ir, "calyx").unwrap();
    // request (Raw) + response (Raw) + 1 matched_value (PatternMatch) = 3 evidence items
    assert_eq!(finding.evidence().len(), 3);
    assert_eq!(
        finding.exploit_hint(),
        Some("curl 'https://example.com/search?q=<script>'")
    );
    assert_eq!(finding.matched_values()[0].as_ref(), "<script>");
}

#[test]
fn empty_template_name_uses_id() {
    let ir = secir::finding::Finding::new(
        "CVE-2021-44228".to_string(),
        String::new(),
        "https://target.com".to_string(),
        secir::Severity::Critical,
        "https://target.com/api".to_string(),
    );

    let finding = to_universal(&ir, "karyx").unwrap();
    assert_eq!(finding.title(), "CVE-2021-44228");
}