clawscan 1.0.0

OpenClaw/Moltbot/Clawdbot vulnerability scanner for prompt injection, supply chain, and RAG poisoning attacks
Documentation
//! Concurrent scan orchestrator with tokio and semaphore-based rate limiting

use crate::{ParsedTarget, AttackReport};
use crate::attacks::{
    execute_cve_2026_25253, execute_cve_2026_22708, execute_cve_2026_25157,
    execute_prompt_injection, execute_rag_poisoning, execute_supply_chain,
    execute_mcp_poisoning, execute_elevated_bypass, execute_zero_click_rce,
};
use anyhow::Result;
use std::sync::Arc;
use tokio::sync::Semaphore;

/// Scan orchestrator configuration
pub struct ScanConfig {
    pub concurrency: usize,
}

impl Default for ScanConfig {
    fn default() -> Self {
        Self { concurrency: 50 }
    }
}

/// Execute all attack modules against a single target
/// Modules run concurrently for faster scanning
pub async fn scan_target(target: &ParsedTarget) -> Result<Vec<AttackReport>> {
    use crate::attacks::check_write_capability;
    use tokio::task;

    let target_clone1 = target.clone();
    let target_clone2 = target.clone();
    let target_clone3 = target.clone();

    // Spawn 3 concurrent task groups
    let group1 = task::spawn(async move {
        // Group 1: Module 1 (CVE-2026-25253 CSWSH with 4 malicious origins)
        execute_cve_2026_25253(&target_clone1).await
    });

    let group2 = task::spawn(async move {
        // Group 2: Shared capability check, then modules 2-8 in parallel
        let capability = check_write_capability(&target_clone2).await?;

        // Spawn modules 2-8 concurrently, passing shared capability
        let t2 = target_clone2.clone();
        let t3 = target_clone2.clone();
        let t4 = target_clone2.clone();
        let t5 = target_clone2.clone();
        let t6 = target_clone2.clone();
        let t7 = target_clone2.clone();
        let t8 = target_clone2.clone();

        let cap2 = capability.clone();
        let cap3 = capability.clone();
        let cap4 = capability.clone();
        let cap5 = capability.clone();
        let cap6 = capability.clone();
        let cap7 = capability.clone();
        let cap8 = capability.clone();

        let m2 = task::spawn(async move { execute_cve_2026_22708(&t2, Some(&cap2)).await });
        let m3 = task::spawn(async move { execute_cve_2026_25157(&t3, Some(&cap3)).await });
        let m4 = task::spawn(async move { execute_prompt_injection(&t4, Some(&cap4)).await });
        let m5 = task::spawn(async move { execute_rag_poisoning(&t5, Some(&cap5)).await });
        let m6 = task::spawn(async move { execute_supply_chain(&t6, Some(&cap6)).await });
        let m7 = task::spawn(async move { execute_mcp_poisoning(&t7, Some(&cap7)).await });
        let m8 = task::spawn(async move { execute_elevated_bypass(&t8, Some(&cap8)).await });

        // Collect results from modules 2-8
        let r2 = m2.await??;
        let r3 = m3.await??;
        let r4 = m4.await??;
        let r5 = m5.await??;
        let r6 = m6.await??;
        let r7 = m7.await??;
        let r8 = m8.await??;

        Ok::<Vec<AttackReport>, anyhow::Error>(vec![r2, r3, r4, r5, r6, r7, r8])
    });

    let group3 = task::spawn(async move {
        // Group 3: Module 9 (Zero-Click RCE - uses origin_accepted check)
        execute_zero_click_rce(&target_clone3).await
    });

    // Wait for all groups to complete
    let report1 = group1.await??;
    let reports2_8 = group2.await??;
    let report9 = group3.await??;

    // Combine all reports in order
    let mut reports = Vec::new();
    reports.push(report1);
    reports.extend(reports2_8);
    reports.push(report9);

    Ok(reports)
}

/// Execute all attack modules against multiple targets concurrently
pub async fn scan_targets(targets: Vec<ParsedTarget>, config: ScanConfig) -> Result<Vec<(ParsedTarget, Vec<AttackReport>)>> {
    use tokio::task;

    // Create semaphore for rate limiting
    let semaphore = Arc::new(Semaphore::new(config.concurrency));
    let mut tasks = Vec::new();

    // Spawn concurrent tasks for each target
    for target in targets {
        let sem = semaphore.clone();
        let task = task::spawn(async move {
            // Acquire semaphore permit
            let _permit = sem.acquire().await.unwrap();

            // Scan the target
            let reports = scan_target(&target).await?;

            Ok::<_, anyhow::Error>((target, reports))
        });

        tasks.push(task);
    }

    // Collect results
    let mut results = Vec::new();
    for task in tasks {
        let result = task.await??;
        results.push(result);
    }

    Ok(results)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_scan_target_executes_all_modules() {
        let target = ParsedTarget {
            url: "ws://test.example.com:18789".to_string(),
            host: "test.example.com".to_string(),
            port: 18789,
        };

        let reports = scan_target(&target).await.unwrap();

        // Should execute all 9 attack modules
        assert_eq!(reports.len(), 9);
    }

    #[tokio::test]
    async fn test_scan_targets_concurrent_execution() {
        let targets = vec![
            ParsedTarget {
                url: "ws://target1.com:18789".to_string(),
                host: "target1.com".to_string(),
                port: 18789,
            },
            ParsedTarget {
                url: "ws://target2.com:18789".to_string(),
                host: "target2.com".to_string(),
                port: 18789,
            },
        ];

        let config = ScanConfig { concurrency: 50 };
        let results = scan_targets(targets.clone(), config).await.unwrap();

        // Should scan both targets
        assert_eq!(results.len(), 2);
        
        // Each target should have 9 attack reports
        for (target, reports) in results {
            assert!(targets.iter().any(|t| t.url == target.url));
            assert_eq!(reports.len(), 9);
        }
    }

    #[tokio::test]
    async fn test_scan_config_default() {
        let config = ScanConfig::default();
        assert_eq!(config.concurrency, 50);
    }

    #[tokio::test]
    async fn test_scan_target_modules_run_concurrently() {
        use std::time::Instant;

        let target = ParsedTarget {
            url: "ws://test.example.com:18789".to_string(),
            host: "test.example.com".to_string(),
            port: 18789,
        };

        let start = Instant::now();
        let reports = scan_target(&target).await.unwrap();
        let duration = start.elapsed();

        // Should still return 9 reports
        assert_eq!(reports.len(), 9);

        // With concurrent execution, should complete much faster than sequential
        // Sequential would be ~6 connection attempts × timeout
        // Concurrent should be ~max(4 CSWSH, 1 capability + modules, 1 zero-click) = ~4 connection timeouts
        // This test just verifies it completes and returns correct number of reports
        // (Actual timing would be flaky in CI)
        assert!(duration.as_secs() < 120, "Scan took too long: {:?}", duration);
    }
}