| Crates.io | scanbridge |
| lib.rs | scanbridge |
| version | 0.3.0 |
| created_at | 2026-01-08 05:52:29.280491+00 |
| updated_at | 2026-01-08 06:55:54.465028+00 |
| description | A unified, pluggable API for malware scanning with circuit breakers, policy enforcement, and audit logging |
| homepage | https://github.com/dotdon/scanbridge |
| repository | https://github.com/dotdon/scanbridge |
| max_upload_size | |
| id | 2029617 |
| size | 341,869 |
A unified, pluggable API for malware scanning in Rust.
Scanbridge provides an abstraction layer over multiple malware scanning engines, with built-in resilience patterns, policy enforcement, quarantine support, and compliance-ready audit logging.
tracing for compliance environmentsAdd to your Cargo.toml:
[dependencies]
scanbridge = "0.1"
tokio = { version = "1", features = ["full"] }
Basic usage:
use scanbridge::prelude::*;
use scanbridge::backends::MockScanner;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a scanner
let scanner = MockScanner::new_clean();
// Build the scan manager
let manager = ScanManager::builder()
.add_scanner(scanner)
.build()?;
// Scan a file
let input = FileInput::from_bytes(b"file content".to_vec());
let context = ScanContext::new().with_tenant_id("my-tenant");
let report = manager.scan(input, context).await?;
if report.is_clean() {
println!("File is clean!");
} else if report.is_infected() {
println!("Threats detected: {:?}", report.all_threats());
}
Ok(())
}
┌─────────────────────────────────────────────────────────────────┐
│ ScanManager │
│ Orchestrates scans, handles retries, manages multiple engines │
└────────────────────────────┬────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ CircuitBreaker │ │ CircuitBreaker │ │ CircuitBreaker │
│ (optional) │ │ (optional) │ │ (optional) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ClamAV │ │ VirusTotal │ │ CustomScanner │
│ Backend │ │ Backend │ │ Backend │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Every scan returns one of four outcomes:
| Outcome | Description |
|---|---|
Clean |
No threats detected |
Infected |
One or more threats found |
Suspicious |
Potentially harmful but not definitive |
Error |
Scan could not complete |
The circuit breaker prevents cascading failures when a scanner becomes unhealthy:
use scanbridge::circuit_breaker::{CircuitBreaker, CircuitBreakerConfig};
use std::time::Duration;
let config = CircuitBreakerConfig::default()
.with_failure_threshold(5) // Open after 5 failures
.with_open_duration(Duration::from_secs(30)) // Stay open 30s
.with_success_threshold(3); // Close after 3 successes
let protected = CircuitBreaker::new(scanner, config);
FailClosed: Reject scans when circuit is open (safest)FailOpen: Allow files through with warning (most available)Fallback(scanner): Use alternate scanner when primary failsDefine rules for handling scan results:
use scanbridge::policy::{PolicyEngine, PolicyRule, Condition, PolicyAction};
let policy = PolicyEngine::new()
.with_rule(
PolicyRule::new("block-infected", PolicyAction::block("Malware detected"))
.with_condition(Condition::is_infected())
.with_priority(100)
)
.with_rule(
PolicyRule::new("quarantine-suspicious", PolicyAction::quarantine("Review needed"))
.with_condition(Condition::is_suspicious())
.with_priority(90)
);
Safely store infected files:
use scanbridge::quarantine::FilesystemQuarantine;
let quarantine = FilesystemQuarantine::new("/var/quarantine")?;
// Files are stored with integrity verification
// and can be retrieved, listed, or deleted
All scan events are emitted via tracing at the scanbridge::audit target:
use tracing_subscriber::fmt::format::FmtSpan;
// Configure a JSON subscriber for compliance logging
tracing_subscriber::fmt()
.json()
.with_target(true)
.init();
Events include:
scan_started / scan_completedpolicy_decisionquarantine_operationImplement the Scanner trait:
use scanbridge::prelude::*;
use async_trait::async_trait;
#[derive(Debug)]
struct MyScanner { /* ... */ }
#[async_trait]
impl Scanner for MyScanner {
fn name(&self) -> &str { "my-scanner" }
async fn scan(&self, input: &FileInput) -> Result<ScanResult, ScanError> {
// Your scanning logic here
todo!()
}
async fn health_check(&self) -> Result<(), ScanError> {
// Verify scanner is operational
Ok(())
}
}
See examples/custom_backend.rs for a complete example.
| Feature | Description | Default |
|---|---|---|
tokio-runtime |
Tokio async runtime support | ✓ |
clamav |
ClamAV backend | ✗ |
virustotal |
VirusTotal API backend | ✗ |
# Basic scanning
cargo run --example basic_scan
# Circuit breaker demonstration
cargo run --example with_circuit_breaker
# Custom backend implementation
cargo run --example custom_backend
Scanbridge never panics. All errors are returned as typed ScanError variants:
EngineUnavailable: Scanner not respondingTimeout: Scan took too longConnectionFailed: Network/socket failureFileTooLarge: File exceeds size limitCircuitOpen: Circuit breaker is openRateLimited: API rate limit exceededAll errors include context about which engine failed and why.
MIT license (LICENSE-MIT)