openclaw-scan 0.1.1

Security scanner for agentic AI framework installations (OpenClaw, Claude Code, and compatible)
Documentation
//! Integration tests for the network-security scanner.

use std::path::PathBuf;

use openclaw_scan::finding::Severity;
use openclaw_scan::paths::FrameworkHint;
use openclaw_scan::scanner::{network::NetworkScanner, ScanContext, Scanner};

fn ctx(root: PathBuf) -> ScanContext {
    ScanContext { root, framework: FrameworkHint::Unknown }
}

fn write_settings(dir: &std::path::Path, json: &str) {
    std::fs::write(dir.join("settings.json"), json).unwrap();
}

#[test]
fn http_external_mcp_flagged_high() {
    let tmp = tempfile::tempdir().unwrap();
    write_settings(
        tmp.path(),
        r#"{
          "mcpServers": {
            "bad": { "type": "sse", "url": "http://api.example.com/mcp" }
          }
        }"#,
    );

    let scanner = NetworkScanner;
    let findings = scanner.scan(&ctx(tmp.path().to_path_buf())).unwrap();

    assert!(!findings.is_empty(), "HTTP external MCP should produce a finding");
    assert!(
        findings.iter().any(|f| f.severity >= Severity::High),
        "HTTP external MCP should be HIGH or above"
    );
}

#[test]
fn https_mcp_no_findings() {
    let tmp = tempfile::tempdir().unwrap();
    write_settings(
        tmp.path(),
        r#"{
          "mcpServers": {
            "good": { "type": "sse", "url": "https://mcp.linear.app/sse" }
          }
        }"#,
    );

    let scanner = NetworkScanner;
    let findings = scanner.scan(&ctx(tmp.path().to_path_buf())).unwrap();

    let high_or_above: Vec<_> = findings.iter().filter(|f| f.severity >= Severity::High).collect();
    assert!(
        high_or_above.is_empty(),
        "HTTPS MCP should not produce HIGH/CRITICAL findings: {:?}",
        high_or_above
    );
}

#[test]
fn bare_ip_mcp_flagged_low() {
    let tmp = tempfile::tempdir().unwrap();
    write_settings(
        tmp.path(),
        r#"{
          "mcpServers": {
            "ip": { "type": "sse", "url": "https://192.168.1.100:8080/mcp" }
          }
        }"#,
    );

    let scanner = NetworkScanner;
    let findings = scanner.scan(&ctx(tmp.path().to_path_buf())).unwrap();

    assert!(!findings.is_empty(), "Bare IP MCP should produce a finding");
    assert!(
        findings.iter().any(|f| f.severity == Severity::Low),
        "Bare IP should be LOW severity"
    );
}

#[test]
fn empty_directory_no_findings() {
    let tmp = tempfile::tempdir().unwrap();
    let scanner = NetworkScanner;
    let findings = scanner.scan(&ctx(tmp.path().to_path_buf())).unwrap();
    assert!(findings.is_empty());
}