A Zentinel agent that scans uploaded files for malware using ClamAV daemon.
- ClamAV Integration: Connects to clamd via Unix socket using INSTREAM protocol
- Content-Type Filtering: Only scan specific content types (or all)
- Path Exclusions: Skip scanning for health checks and other paths
- Method Filtering: Configure which HTTP methods to scan (POST, PUT, PATCH)
- Size Limits: Skip scanning for bodies exceeding configured size
- Fail-Open/Closed: Configurable behavior when ClamAV is unavailable
- Scan Metrics: Headers include scan time and detection status
# Install just this agent
zentinel bundle install content-scanner
# Or install all bundled agents
zentinel bundle installThe bundle command downloads the correct binary for your platform and places it in the standard location. See the bundle documentation for details.
cargo install zentinel-agent-content-scannergit clone https://github.com/zentinelproxy/zentinel-agent-content-scanner
cd zentinel-agent-content-scanner
cargo build --releaseCreate a config.yaml file:
# Content Scanner Agent Configuration
settings:
enabled: true
fail_action: allow # allow or block when ClamAV unavailable
log_detections: true
log_clean: false
# Body handling
body:
max_size: 52428800 # 50MB max body to scan
content_types: # Only scan these content types (empty = all)
- "application/octet-stream"
- "application/zip"
- "application/x-zip-compressed"
- "application/gzip"
- "application/x-gzip"
- "application/x-tar"
- "application/pdf"
- "application/msword"
- "application/vnd.openxmlformats-officedocument.*"
- "multipart/form-data"
# ClamAV daemon configuration
clamd:
enabled: true
socket_path: "/var/run/clamav/clamd.ctl"
timeout_ms: 30000 # 30 second scan timeout
chunk_size: 65536 # 64KB chunks to clamd
# Paths to skip scanning
skip_paths:
- "/health"
- "/ready"
- "/metrics"
# Methods to scan (empty = POST, PUT, PATCH)
scan_methods:
- "POST"
- "PUT"
- "PATCH"# Run with default config
./zentinel-content-scanner-agent -c config.yaml
# Run with custom socket path
./zentinel-content-scanner-agent -c config.yaml -s /tmp/content-scanner.sock
# Print example configuration
./zentinel-content-scanner-agent --example-config
# Validate configuration
./zentinel-content-scanner-agent -c config.yaml --validateThe agent requires ClamAV daemon (clamd) to be running and accessible via Unix socket.
brew install clamav
# Initialize database
freshclam
# Start daemon
clamdsudo apt-get install clamav-daemon
sudo systemctl start clamav-daemonsudo yum install clamav-server clamav-update
sudo freshclam
sudo systemctl start clamd@scan| Header | Description |
|---|---|
x-content-scanned |
Set to "true" when body was scanned |
x-scan-time-ms |
Scan duration in milliseconds |
x-malware-detected |
Set to "true" when malware found |
x-malware-name |
Virus/malware signature name |
x-scan-skipped |
Reason scan was skipped (size-exceeded, content-type-excluded, clamd-unavailable) |
The agent supports flexible content-type matching:
- Exact match:
application/json - Wildcard subtype:
application/*matches any application type - Glob suffix:
application/vnd.*matchesapplication/vnd.ms-excel
Test with EICAR standard antivirus test string:
# Clean file (should return 200)
echo "Hello World" | curl -X POST -H "Content-Type: application/octet-stream" -d @- http://localhost:8080/upload
# EICAR test file (should return 403)
echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' \
| curl -X POST -H "Content-Type: application/octet-stream" -d @- http://localhost:8080/uploadConfiguration values can use environment variables with ${VAR_NAME} syntax:
clamd:
socket_path: "${CLAMD_SOCKET_PATH}"MIT