Suricata, Supercharged.
A powerful bolt-on toolkit that transforms Suricata into a capable NDR with AI-powered detection and automated response.
| Suricata-Native | Reads EVE JSON directly from Unix socket. |
| Columnar Storage | Apache Parquet with Zstd compression for fast analytical queries. |
| Behavioral Detection | Graph-based threat hunting: beaconing, lateral movement, exfiltration. |
| Automated Response | Publish alerts to MQTT, Kafka, or webhooks for n8n, Fluent Bit, Vector, or SIEM integration. |
| Interactive Reports | Self-contained HTML dashboards with Chart.js and D3.js. |
| AI-Ready | MCP server and chat interface for conversational network analysis. |
| Air-Gap Ready | Fully offline operation. No cloud dependencies required. |
| Single Binary | Rust. No runtime dependencies. Deploy from .deb package or Docker container. |
Architecture
Suricata EVE JSON ──► rockfish occam ──► Parquet ──► S3 (optional)
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
Report Hunt MCP/Chat
(HTML pages) (threat detection) (AI-native queries)
│
▼
Alert
(MQTT / Kafka / Webhook)
What Is an Event?
An event is a single JSON record from Suricata’s EVE log — one line, one record. Every alert, flow, DNS transaction, HTTP request, TLS handshake, or protocol log entry is one event. Event rate limits in license tiers are measured by counting these individual JSON records per minute.
Core Pipeline
Input — Rockfish connects directly to Suricata’s EVE output via Unix socket for real-time streaming or file tailing for batch processing. No agents. No sidecars. No middleware.
Parse — Events are strongly typed, parsed into native Rust structures, and routed by event type. Optional enrichment layers add GeoIP geolocation and IP reputation scoring before writing to columnar storage. Configurable include/exclude event filtering, memory-bounded buffering, and multi-sensor partitioning for distributed deployments.
Store — All events are written to Apache Parquet with Zstd compression and hive-style date partitioning. Embedded DuckDB provides sub-second analytical SQL at query time. Optional AWS S3 / MinIO / DigitalOcean Spaces upload for long-term retention.
Analyze — Three complementary engines operate on the same Parquet data. Hunt builds communication graphs and applies 12 behavioral detection algorithms (beaconing, lateral movement, C2 fanout, port scanning, DNS tunneling, data exfiltration, and more) with ML-based anomaly detection for unknown threats. Report renders 12+ page self-contained HTML dashboards with Chart.js and D3. MCP and Chat expose data to AI assistants for conversational investigation.
Respond — Detection findings and enriched alerts are published to MQTT, Kafka, and webhooks for downstream automation. Integrate with Fluent Bit, Vector, n8n, Node-RED, or any consumer for SIEM forwarding and SOAR workflows.
Commands at a Glance
| Command | Description |
|---|---|
rockfish occam | Ingest EVE JSON, run OCCAM detection and behavioral threat hunting |
rockfish report | Generate static HTML NDR report |
rockfish alert | Publish detection events to MQTT, Kafka, or webhooks |
rockfish mcp | Start MCP server for AI-powered queries |
rockfish chat | Start AI chat server for NDR data analysis |
rockfish http | Serve report pages over HTTP with authentication |
rockfish auto | Run hunt + report automatically at set intervals |
rockfish prune | Remove old Parquet files by retention policy |
rockfish compact | Compact and merge Parquet files for storage efficiency |
rockfish update | Download and install Suricata rule updates |
rockfish config | Show resolved configuration and features |
rockfish stats | Parse EVE JSON and show event type statistics |
Technical Foundation
- Language: Rust — single static binary, no runtime dependencies
- Query Engine: Embedded DuckDB for analytical SQL on Parquet
- Storage Format: Apache Parquet with Zstd compression
- Protocols: MQTT, Kafka, Webhooks, MCP (Model Context Protocol)
- Crypto: Ed25519 license verification, TLS 1.3 transport
- Concurrency: Rayon parallel query execution, Tokio async I/O
Companion Repositories
- Rockfish Toolkit — Suricata plugins that complement Rockfish NDR: 16+ OT/IIoT application-layer parsers (Modbus, DNP3, S7comm, OPC UA, MQTT, BACnet, IEC 61850, IEC 60870-5-104, EtherNet/IP, EtherCAT, PROFINET, RTPS/DDS, ASTERIX, and more), the
payload_entropyplugin for encrypted-traffic analysis, thetransport_perfplugin for per-flow TCP/UDP performance, and the FMADIO ring-buffer capture plugin. All emit structured events through Suricata’s eve-log into the Rockfish NDR pipeline.
Next Steps
- Quick Start - Get up and running in minutes
- Configuration - YAML configuration reference
Quick Start
Deploy Rockfish NDR in under 30 minutes. This guide covers detection, reporting, and rule management.
Architecture
Rockfish NDR runs as two services:
| Service | Command | What it does |
|---|---|---|
| Detection | rockfish occam | OCCAM engine + Parquet ingest + Hunt |
| Reporting | rockfish report | HTML dashboard + HTTP server + AI Insight |
Both read from the same Parquet data directory.
1. Install
cd /develop/rockfish/ndr
./scripts/build-cli.sh --install # rockfish + rockfish-curator → /opt/rockfish/bin/
./scripts/build-rules.sh --install # rockfish-ruleset → /opt/rockfish/bin/
2. Configure Suricata Rules
sudo -u rockfish rockfish-curator select --count 256 \
--cache /var/lib/rockfish/et-open-cache \
--output /var/lib/rockfish/staging
sudo rockfish-ruleset refresh \
--suricata-socket /var/run/suricata/suricata-command.socket \
--suricata-binary /opt/suricata/bin/suricata
3. Start Detection
From a File
# Ingest EVE JSON into Parquet
rockfish ingest -i /var/log/suricata/eve.json \
-o /data/rockfish --sensor my-sensor --hive
Continuous Ingestion
# Follow mode — tails the log like tail -F
rockfish ingest -i /var/log/suricata/eve.json \
-o /data/rockfish --sensor my-sensor --hive --follow
# From a Unix socket (Suricata unix_stream output)
rockfish ingest --socket /var/run/suricata/eve.sock \
-o /data/rockfish --sensor my-sensor --hive
Verify Output
ls -la /data/rockfish/my-sensor/
# alert/ flow/ dns/ http/ tls/ ...
2. Run Threat Detection
# Hunt across the last 24 hours
rockfish hunt -d /data/rockfish --sensor my-sensor --hive \
-t "24 hours"
Hunt findings are written to /data/rockfish/my-sensor/hunt/*.parquet.
View Results on Stdout
# Pretty-printed JSON
rockfish hunt -d /data/rockfish --sensor my-sensor --hive \
--stdout --pretty
# Table format
rockfish hunt -d /data/rockfish --sensor my-sensor --hive \
--stdout --format table
3. Generate HTML Report
# Generate report for the last 24 hours
rockfish report -d /data/rockfish --sensor my-sensor --hive \
-t "24 hours" -o /var/www/html/ndr
Open report/index.html in a browser to view the dashboard.
Demo Mode
Generate a report with synthetic data to see all features:
rockfish report --demo -o ./demo-report
4. Publish Alerts
# Publish to MQTT broker
rockfish alert -d /data/rockfish --sensor my-sensor --hive \
--mqtt-broker mosquitto -t "1 hour"
# Continuous publishing
rockfish alert -d /data/rockfish --sensor my-sensor --hive \
--mqtt-broker mosquitto --continuous
Subscribe to Alerts
# In another terminal, subscribe to all rockfish topics
mosquitto_sub -t 'rockfish/#' -v
5. Continuous Operation
Run all components together for ongoing monitoring:
# Terminal 1: Continuous ingestion
rockfish ingest --socket /var/run/suricata/eve.sock \
-o /data/rockfish --sensor prod-01 --hive
# Terminal 2: Hourly threat hunts
rockfish hunt -d /data/rockfish --sensor prod-01 --hive \
--continuous --interval-minutes 60
# Terminal 3: Report regeneration every 5 minutes
rockfish report -d /data/rockfish --sensor prod-01 --hive \
--continuous --interval-minutes 5
# Terminal 4: Alert publishing
rockfish alert -d /data/rockfish --sensor prod-01 --hive \
--mqtt-broker mosquitto --continuous
Using a Configuration File
Create rockfish.yaml to avoid repeating CLI arguments:
sensor:
name: prod-01
input:
socket: /var/run/suricata/eve.sock
output:
dir: /data/rockfish
hive_partitioning: true
compression: zstd
s3:
bucket: rockfish-data
region: us-east-1
alert:
mqtt:
broker: mosquitto
port: 1883
topic_prefix: rockfish
rockfish -c rockfish.yaml ingest
rockfish -c rockfish.yaml hunt --continuous
rockfish -c rockfish.yaml report --continuous
rockfish -c rockfish.yaml alert --continuous
Next Steps
- Configuration - Full YAML configuration reference
- rockfish ingest - Input sources, S3 upload, event filtering
- rockfish hunt - Detection types and tuning
- rockfish report - Report pages and theming
- rockfish alert - MQTT/Kafka publishing
Installation
Quick Install
curl -fsSL https://docs.rockfishndr.com/install.sh | bash
The installer auto-detects your platform and installs via the appropriate method:
- Debian/Ubuntu: APT repository (recommended)
- Other Linux: Docker or binary
- macOS: Docker or binary
Options:
# Install specific version
ROCKFISH_VERSION=1.0.0 curl -fsSL https://docs.rockfishndr.com/install.sh | bash
# Force specific installation method
ROCKFISH_METHOD=apt curl -fsSL https://docs.rockfishndr.com/install.sh | bash
ROCKFISH_METHOD=docker curl -fsSL https://docs.rockfishndr.com/install.sh | bash
APT Repository (Debian/Ubuntu)
The recommended installation method for Debian-based systems. Enables automatic updates via apt-get upgrade.
Install
Run the four commands below. Each is intentionally a single line so you can copy and paste them straight into a terminal.
sudo curl -fsSLo /usr/share/keyrings/rockfish-archive-keyring.gpg https://repo.rockfishndr.com/rockfish-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/rockfish-archive-keyring.gpg] https://repo.rockfishndr.com stable main" | sudo tee /etc/apt/sources.list.d/rockfish.list
sudo apt update
sudo apt install rockfish
Update
sudo apt update && sudo apt upgrade rockfish
System Requirements
- Operating System: Debian 11+, Ubuntu 20.04+, or Docker-compatible host
- Architecture: x86_64 (amd64), ARM64 (arm64)
- Memory: 2GB minimum (4GB+ recommended for high-traffic networks)
- Storage: Depends on retention policy (10GB minimum)
Installation Directory Structure
After installation, Rockfish NDR is installed to /opt/rockfish:
/opt/rockfish/
├── bin/ # Compiled binaries
├── etc/ # Configuration files
├── lib/ # DuckDB extensions and libraries
└── example/ # Example systemd services and configs
System Directories
| Path | Description |
|---|---|
/opt/rockfish/bin/ | Rockfish binaries |
/opt/rockfish/etc/ | Configuration directory |
/opt/rockfish/example/ | Example configs and systemd services |
/var/lib/rockfish/ | Data directory |
/var/log/rockfish/ | Log directory |
/var/run/rockfish/ | Runtime directory |
Configuration
Configuration File
# Copy or create configuration
sudo cp /opt/rockfish/example/rockfish.yaml.example /opt/rockfish/etc/rockfish.yaml
Rockfish searches for configuration in this order:
--config <path>(CLI argument)./rockfish.yaml/etc/rockfish/rockfish.yaml~/.config/rockfish/rockfish.yaml
Environment File
Credentials and secrets are stored in an environment file:
# Create environment file
cat > /opt/rockfish/etc/rockfish.env << 'EOF'
ROCKFISH_S3_BUCKET=rockfish-data
ROCKFISH_S3_REGION=us-east-1
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
ABUSEIPDB_API_KEY=...
EOF
Systemd Services
The package installs systemd service files to /lib/systemd/system/. To enable and start a service:
# Reload systemd to pick up new service files
sudo systemctl daemon-reload
# Enable service to start on boot
sudo systemctl enable rockfish
# Start the service
sudo systemctl start rockfish
# Check status
sudo systemctl status rockfish
# View logs
sudo journalctl -u rockfish -f
Docker Installation
Pull the Rockfish NDR image from Docker Hub:
docker pull rockfishnetworks/toolkit:latest
The toolkit image includes the Rockfish binary with all features enabled.
Running Rockfish (Ingest Mode)
docker run -d \
--name rockfish \
-v /opt/rockfish/etc:/opt/rockfish/etc:ro \
-v /data/rockfish:/data/rockfish \
-p 3000:3000 \
-p 8082:8082 \
rockfishnetworks/toolkit:latest \
rockfish ingest --socket /var/run/suricata/eve.sock
| Port | Service |
|---|---|
3000 | MCP server |
8082 | Chat server |
Docker Compose
Example docker-compose.yml:
version: '3.8'
services:
rockfish:
image: rockfishnetworks/toolkit:latest
ports:
- "3000:3000"
- "8082:8082"
volumes:
- ./config:/opt/rockfish/etc:ro
- ./data:/data/rockfish
command: ["rockfish", "ingest", "--socket", "/var/run/suricata/eve.sock"]
restart: unless-stopped
Verify Installation
# Check version
rockfish --version
# Show configuration and features
rockfish config
Uninstalling
APT Package
# Remove package (keeps configuration)
sudo apt remove rockfish
# Remove package and configuration
sudo apt purge rockfish
# Remove repository and key
sudo rm /etc/apt/sources.list.d/rockfish.list
sudo rm /usr/share/keyrings/rockfish-archive-keyring.gpg
Docker
docker stop rockfish
docker rm rockfish
docker rmi rockfishnetworks/toolkit:latest
Next Steps
- Quick Start - Ingest, hunt, and report in minutes
- Configuration - Full YAML configuration reference
Configuration
Rockfish NDR uses YAML-based configuration with CLI overrides and environment-file credential management.
Configuration Search Paths
Rockfish searches for configuration in this order:
--config <path>(CLI argument)./rockfish.yaml/etc/rockfish/rockfish.yaml~/.config/rockfish/rockfish.yaml
Full Configuration Reference
# ============================================================
# Sensor
# ============================================================
sensor:
name: prod-sensor-01 # Sensor name (default: hostname)
# ============================================================
# Input — EVE JSON source
# ============================================================
input:
file: /var/log/suricata/eve.json # Path to EVE JSON file
socket: /var/run/suricata/eve.sock # Or: Unix socket path
socket_type: stream # stream (default) or dgram
follow: true # Tail file like tail -F
# ============================================================
# Output — Parquet destination
# ============================================================
output:
dir: /data/rockfish # Output directory
hive_partitioning: true # year=YYYY/month=MM/day=DD/
compression: zstd # none, snappy, zstd
flush_interval: 60 # Seconds between flushes
memory_threshold: 1073741824 # 1 GB memory flush threshold
partition: true # Partition by event type
# ============================================================
# Event Filtering
# ============================================================
events:
include: # Only process these types
- alert
- flow
- dns
- http
- tls
exclude: # Skip these types
- stats
# ============================================================
# S3 Upload
# ============================================================
s3:
bucket: rockfish-data
region: us-east-1
prefix: "" # Optional key prefix
delete_after_upload: false # Delete local files after upload
# ============================================================
# Report
# ============================================================
report:
output_dir: ./report
time_window: "24 hours"
theme: /etc/rockfish/theme.yaml # Optional theme file
custom_css: "" # Optional custom CSS path
# ============================================================
# Hunt
# ============================================================
hunt:
time_window: "24 hours"
detections: "beaconing,lateral,fanout,portscan,community"
min_severity: medium
scoring_method: iforest # iforest (default) or hbos
internal_networks: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
# ============================================================
# Alert — MQTT and Kafka publishing
# ============================================================
alert:
mqtt:
broker: localhost
port: 1883
client_id: rockfish-alert
qos: 1
topic_prefix: rockfish
username: ""
password: ""
tls_enabled: false
kafka: # Optional — requires kafka feature
brokers: "localhost:9092"
topic_prefix: rockfish
client_id: rockfish-alert
security_protocol: plaintext
compression: none
confidence_threshold: 0.75
poll_interval_secs: 30
heartbeat_interval_secs: 60
dedup_window_secs: 300
enabled_types:
- signature
- lateral_movement
- c2_beacon
- exfiltration
- anomaly
# ============================================================
# Enrichment
# ============================================================
enrichment:
geoip:
database_path: /usr/share/GeoIP/GeoLite2-City.mmdb
asn_database_path: /usr/share/GeoIP/GeoLite2-ASN.mmdb
ip_reputation:
enabled: true
api_key: ${ABUSEIPDB_API_KEY}
cache_path: /var/lib/rockfish/ip_cache.parquet
cache_ttl_hours: 48
memory_cache_size: 50000
lookup_timeout_ms: 200
# ============================================================
# Data Retention
# ============================================================
retention: 30d # 30 days (supports: 7d, 24h, etc.)
# ============================================================
# HTTP Server
# ============================================================
http:
dir: /var/lib/report # Directory to serve
host: 127.0.0.1 # Bind address
port: 8001 # Bind port
users_file: /opt/rockfish/etc/users # Password file path
session_expiry_hours: 24 # Session cookie lifetime
auth: true # Enable authentication (false to disable)
# ============================================================
# License
# ============================================================
license: /etc/rockfish/license.json
Environment File
Credentials and secrets should be stored in an environment file rather than the YAML config:
# /opt/rockfish/etc/rockfish.env
ROCKFISH_S3_BUCKET=rockfish-data
ROCKFISH_S3_REGION=us-east-1
AWS_ACCESS_KEY_ID=AKIAEXAMPLE
AWS_SECRET_ACCESS_KEY=secretkey
ABUSEIPDB_API_KEY=your-api-key
MQTT_PASSWORD=broker-password
KAFKA_PASSWORD=kafka-password
The environment file path defaults to /opt/rockfish/etc/rockfish.env and can be overridden with --env-file.
CLI Overrides
CLI arguments override YAML configuration values:
# Override sensor name and data directory
rockfish -c rockfish.yaml ingest --sensor custom-name -o /tmp/data
# Override MQTT broker for alert command
rockfish -c rockfish.yaml alert --mqtt-broker custom-host
Environment Variable Overrides
Alert command options can also be set via environment variables:
| Variable | Description |
|---|---|
MQTT_BROKER | MQTT broker hostname |
MQTT_PORT | MQTT broker port |
MQTT_USERNAME | MQTT authentication username |
MQTT_PASSWORD | MQTT authentication password |
MQTT_CLIENT_ID | MQTT client identifier |
MQTT_TOPIC_PREFIX | MQTT topic prefix |
KAFKA_ENABLED | Enable Kafka transport |
KAFKA_BROKERS | Kafka broker addresses |
KAFKA_USERNAME | Kafka SASL username |
KAFKA_PASSWORD | Kafka SASL password |
CONFIDENCE_THRESHOLD | Minimum alert confidence |
Licensing
Rockfish NDR uses Ed25519-signed license files for offline-verifiable feature gating.
Tiers
| Tier | Events/min | Segments | IP Rep | Anomaly/Behavioral/Hunt | MCP/Chat | Scope |
|---|---|---|---|---|---|---|
| Free | 10,000 | 1 | — | — | — | 1 sensor |
| Basic | 25,000 | 1 | Yes | — | — | 1 sensor |
| Professional | 100,000 | 8 | Yes | Yes | — | 1 sensor |
| Enterprise | Unlimited | Unlimited | Yes | Yes | Yes | Site (unlimited sensors) |
See License Tiers for the full feature matrix.
45-Day Trial
There are two try-before-you-buy windows, both 45 days:
- No license file — the CLI runs at the Free tier but auto-bumps to Basic features for the first 45 days from its build date, then settles back to Free.
- Any issued license — bumped to Professional features for the first
45 days from
issued_at(the trial caps at Professional; Enterprise features remain paid-only, and an Enterprise license is never down-ranked). After 45 days the license settles to its purchased tier.
The NDR engine re-checks the license once per day.
Purchasing
Licenses are purchased through the Rockfish Portal. Free, Basic, and Professional licenses each cover one Suricata instance; multiple sensors require multiple licenses. Enterprise is a site license that covers an unlimited number of sensors at a single site.
Installation
scp rockfish-license.json root@sensor:/opt/rockfish/etc/rockfish_license.json
rockfish occam --license /opt/rockfish/etc/rockfish_license.json
No License
Running without a license defaults to the Free tier (10K events/min, core panels, OT decoders, GeoIP, Parquet to S3 export, reports), with the 45-day Basic trial described above.
Expiry Reminders
Licenses are issued on an annual (per-year) basis with a 30-day grace
period past expires_at. Email reminders are sent at 30, 7, 1, and 0 days
before expiry. After expiry and the grace window, the engine falls back to
the Free tier.
Tier Details
Free
Available without a license file:
- Core panels, OT protocol decoders, S3 backhaul
- GeoIP enrichment + world map
- NIST PQC compliance, Encrypted Traffic Analytics, Performance lens
- HTML reports and full documentation
Basic
- Everything in Free
- IP reputation scoring (AbuseIPDB)
Professional
- Everything in Basic
- Anomaly (iForest/HBOS), Behavioral (SIGMA + OCCAM), Hunt detection
- Asset Inventory page, OT Protocol Traffic panel
- Per-segment sub-reports, Parquet signing, webhook publishing
Enterprise
- Everything in Professional
- Detection swimlane + topology graph, AI Assessment
- MCP + Chat, custom theme + logo, MQTT/Kafka, external threat intel
Deployment
- Runs on your VPC or on-premise
- No telemetry or phone home
- Fully air-gap capable
- Ed25519-signed licenses with provenance metadata included in every Parquet file
License File
Licenses are JSON files with an Ed25519 signature:
{
"id": "rockfish_acme-corp-enterprise_Abc123",
"tier": "enterprise",
"customer_name": "Acme Corp",
"customer_email": "[email protected]",
"max_events_per_min": null,
"issued_at": "2026-01-01T00:00:00Z",
"expires_at": "2027-01-01T00:00:00Z",
"signature": "base64-encoded-ed25519-signature"
}
Configuration
Specify the license file on the command line or in YAML config:
# CLI argument
rockfish --license /etc/rockfish/license.json occam
# Or in rockfish.yaml
license: /etc/rockfish/license.json
Verify License
# Show license information with rockfish config
rockfish --license /etc/rockfish/license.json config
Next Steps
- License Tiers - Detailed feature matrix
rockfish occam
The primary detection service — runs the OCCAM detection engine with integrated Parquet ingest and behavioral threat hunting.
Overview
rockfish occam is the main detection command that replaces the previous rockfish run and rockfish ingest commands. It combines:
- OCCAM detection engine — tokenizes EVE events, builds HBOS baselines, scores anomalies
- Parquet ingest — writes EVE events to Hive-partitioned Parquet files
- Hunt — periodic behavioral threat detection (beaconing, lateral movement, C2, etc.)
Usage
rockfish occam \
--socket /var/run/rockfish/rockfish.sock \
--output-dir /var/lib/rockfish/detections \
--parquet-dir /var/lib/rockfish/parquet \
--hunt --hunt-interval 60 \
--license /opt/rockfish/etc/rockfish_license.json
Options
| Flag | Default | Description |
|---|---|---|
--socket <path> | — | Unix socket for EVE input (Suricata connects to this) |
--eve-file <path> | — | EVE JSON file input (alternative to socket) |
--follow | false | Tail mode for file input |
--output-dir <path> | /var/lib/rockfish/detections | Detection JSONL output directory |
--parquet-dir <path> | — | Parquet output directory (enables EVE-to-Parquet ingest) |
--hunt | false | Enable periodic behavioral threat detection |
--hunt-interval <min> | 60 | Minutes between hunt runs |
--window-minutes <min> | 15 | OCCAM window duration |
--baseline-min-days <days> | 7 | Days before HBOS baseline activates |
--baseline-min-samples <n> | 256 | Minimum samples before baseline activates |
--surprisal-threshold <bits> | 1.5 | Default surprisal threshold |
--status-interval <sec> | 60 | Status line interval (0 to disable) |
--alert-webhook <url> | — | POST elevated detections to this URL |
--flush-interval <sec> | 60 | Parquet flush interval |
--compression <codec> | zstd | Parquet compression (none, snappy, zstd) |
--license <path> | — | License JSON file |
--sensor <name> | hostname | Sensor name for partitioning |
Systemd Service
[Unit]
Description=Rockfish NDR — OCCAM Detection Engine
After=network.target
Before=suricata.service
[Service]
Type=simple
User=rockfish
Group=rockfish
ExecStart=/opt/rockfish/bin/rockfish occam \
--socket /var/run/rockfish/rockfish.sock \
--output-dir /var/lib/rockfish/detections \
--parquet-dir /var/lib/rockfish/parquet \
--hunt --hunt-interval 60 \
--license /opt/rockfish/etc/rockfish_license.json
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Socket Mode
Rockfish creates a Unix socket and listens for Suricata to connect. Suricata must be configured to output EVE JSON to this socket. The order matters:
- Start
rockfish occam(creates socket, waits for connection) - Start Suricata (connects to the socket)
Detection Output
OCCAM detections are written to both:
- JSONL —
{output-dir}/{sensor}/sigma/year=YYYY/month=MM/day=DD/detections.jsonl - Parquet —
{parquet-dir}/{sensor}/sigma/year=YYYY/month=MM/day=DD/{timestamp}.parquet
The Parquet output is used by rockfish report for the Insight (AI assessment) and OCCAM dashboard pages.
Hunt Thread
When --hunt is enabled, a background thread runs DuckDB queries on the Parquet flow data at the specified interval. Detections include:
- Beaconing (periodic C2 check-ins)
- Lateral movement (internal-to-internal spread)
- C2 fanout (single source → many destinations)
- Port scanning
- Community detection (clusters of communicating hosts)
45-Day Trial Window
All licenses receive Professional features for 45 days from issued_at (the trial caps at Professional; Enterprise features remain paid-only, and an Enterprise license is never down-ranked). After 45 days, the license settles to its purchased tier. The OCCAM engine re-checks the license once per day.
rockfish report
The reporting service — generates HTML dashboards, serves them over HTTP, and manages compact/prune operations.
Overview
rockfish report runs as a separate service from rockfish occam. It reads Parquet data produced by the OCCAM engine and generates a static HTML dashboard that can be served over HTTP.
It combines:
- HTML report generation — continuous, regenerates every 10 minutes (configurable)
- HTTP server — serves reports via
--serve - Compact — merges small Parquet files hourly
- Prune — removes old Parquet files daily (90-day retention)
- Insight — AI-generated security assessment (daily, requires
ANTHROPIC_API_KEY)
Usage
rockfish report \
--data-dir /var/lib/rockfish/parquet \
--sensor fmadio20p3-798-ubuntu22 \
--hive \
--output-dir /var/lib/rockfish/reports \
--continuous \
--interval-minutes 10 \
--serve --port 8080 \
--license /opt/rockfish/etc/rockfish_license.json
Systemd Service
[Unit]
Description=Rockfish NDR — Reporting Service
After=network.target rockfish.service
[Service]
Type=simple
User=rockfish
Group=rockfish
ExecStart=/opt/rockfish/bin/rockfish report \
--data-dir /var/lib/rockfish/parquet \
--sensor fmadio20p3-798-ubuntu22 \
--hive \
--output-dir /var/lib/rockfish/reports \
--continuous \
--interval-minutes 10 \
--serve --port 8080 \
--license /opt/rockfish/etc/rockfish_license.json
Restart=on-failure
[Install]
WantedBy=multi-user.target
Background Threads
| Thread | Schedule | Description |
|---|---|---|
| Report | Every 10 min | Regenerates HTML dashboard |
| HTTP | Always on | Serves reports on configured port |
| Compact | Hourly | Merges small Parquet files |
| Prune | Daily (midnight UTC) | Deletes files older than 90 days |
Insight (AI Assessment)
When ANTHROPIC_API_KEY is set, the report generates an AI-powered security assessment once per day.
ANTHROPIC_API_KEY— requiredANTHROPIC_MODEL— default:claude-haiku-4-5-20251001
Overview
The report command produces interactive HTML dashboards with Chart.js and D3.js visualizations — no web server required. Reports include 12+ pages covering alerts, threats, DNS, TLS, flows, hosts, network topology, asset inventory, and hunt findings.
Usage
rockfish report [OPTIONS]
Report Pages
| Page | Highlights |
|---|---|
| Overview | Traffic volume, hourly charts, event counts, top talkers, protocol breakdown |
| Alerts | Severity timeline, top signatures, alerted hosts, MITRE ATT&CK mapping |
| Findings | Hunt detection results by severity and type, evidence table |
| Threats | IP reputation, beaconing, large transfers, DGA, DNS tunneling, port scans |
| DNS | Top domains, response codes (NOERROR, NXDOMAIN, SERVFAIL), DGA indicators |
| TLS | Version distribution, SNI hostnames, JA3 fingerprints, self-signed certs |
| Applications | Protocol distribution, hourly stacked charts, top HTTP hosts |
| Flows | Volume and direction, destination ports, top countries (GeoIP) |
| Hosts | Top alerted hosts, top talkers by flow count and volume |
| Network | Force-directed graph with IP/24/16 aggregation, threat and anomaly overlays |
| Inventory | Passive device discovery, device roles, OT protocol summary |
| Query | Conversational AI interface (requires rockfish chat) |
Visualization Features
- World Map — Leaflet.js with country-level flow, alert, and reputation overlays
- Network Graph — D3.js force-directed topology with Flows/Alerts/Hunt toggle layers, including anomaly (iForest/HBOS) findings overlay
- Heat-Mapped Tables — Gradient backgrounds for volume, severity, and scores
- Collapsible Tables — Expand/collapse with JSON export
- Severity Colors — Consistent palette: critical (red) through info (blue)
Options
| Option | Default | Description |
|---|---|---|
-d, --data-dir | ./output | Data directory with Parquet files |
--sensor | sensor | Sensor name subdirectory |
--hive | — | Enable hive-style partitioning |
-o, --output-dir | ./report | Output directory for HTML |
-t, --time-window | 24 hours | Time window filter |
--theme | — | YAML theme configuration |
--custom-css | — | Custom CSS file path |
--continuous | — | Regenerate on schedule |
--interval-minutes | 5 | Minutes between regenerations |
Theming
Customize report appearance with a YAML theme file:
# theme.yaml
background: "#0d1117"
surface: "#161b22"
text: "#e6edf3"
text_heading: "#ffffff"
accent: "#1a73e8"
rockfish report -d /data --sensor my-sensor --theme theme.yaml
See theme.yaml.example for all available options.
Custom Logo
Replace the default Rockfish logo with your own branding. Requires an Enterprise license.
# theme.yaml
logo_path: "/path/to/your-logo.png"
| Property | Value |
|---|---|
| Formats | PNG, JPEG |
| Recommended size | 200 x 36 pixels |
| Display height | 36px (width scales proportionally) |
The logo appears in the header bar of every report page.
Demo Mode
Generate a report with synthetic data to showcase all features:
rockfish report --demo -o ./demo-report
Demo mode is available on all license tiers.
Continuous Mode
# Regenerate every 5 minutes (default)
rockfish report -d /data --sensor my-sensor --hive --continuous
# Regenerate every 15 minutes
rockfish report -d /data --sensor my-sensor --hive \
--continuous --interval-minutes 15
Examples
# 24-hour report
rockfish report -d /data/rockfish --sensor prod-01 --hive \
-o /var/www/html/ndr
# 7-day report with custom theme
rockfish report -d /data --sensor prod-01 --hive \
-t "7 days" --theme /etc/rockfish/theme.yaml
# Continuous regeneration for live dashboard
rockfish report -d /data --sensor prod-01 --hive \
--continuous --interval-minutes 10 -o /var/www/html/ndr
rockfish hunt
Run graph-based behavioral threat detection on Parquet flow data.
Overview
The hunt engine builds a communication graph from network flow data and applies configurable detection algorithms to identify threats beyond signature matching — C2 beaconing, lateral movement, data exfiltration, and more.
Findings are scored with anomaly detection models (HBOS or Isolation Forest), assigned severity levels, and mapped to MITRE ATT&CK tactics.
Usage
rockfish hunt [OPTIONS]
Detection Types
| Detection | Description | MITRE Tactic |
|---|---|---|
| beaconing | C2 callbacks via inter-connection timing regularity | Command and Control |
| lateral | Multi-hop internal attack chains (A -> B -> C) | Lateral Movement |
| fanout | Single external IP contacted by many internal hosts | Command and Control |
| portscan | Hosts probing many unique ports on a target | Discovery |
| community | Botnet-like clusters via graph components | Command and Control |
| exfiltration | Asymmetric flows with disproportionate outbound volume | Exfiltration |
| dns_tunneling | DNS queries with long or encoded subdomains | Command and Control |
| new_connection | Source-destination pairs absent from 7-day baseline | Initial Access |
| polling_disruption | Interruption of periodic communication | Impact |
| baseline_deviation | Volume or pattern shifts vs. historical norms | Discovery |
Select Specific Detections
rockfish hunt -d /data --sensor my-sensor --hive \
--detections beaconing,lateral,fanout
Output
Parquet (default)
Findings are written to {data-dir}/{sensor}/hunt/*.parquet for ingestion into the report.
Stdout
# JSON output
rockfish hunt -d /data --sensor my-sensor --stdout
# Pretty-printed JSON
rockfish hunt -d /data --sensor my-sensor --stdout --pretty
# Table format
rockfish hunt -d /data --sensor my-sensor --stdout --format table
Severity Filtering
# Only high and critical findings
rockfish hunt -d /data --sensor my-sensor --min-severity high
Tuning
| Option | Default | Description |
|---|---|---|
--min-beacon-connections | auto | Minimum connections for beacon detection |
--max-beacon-cv | auto | Maximum coefficient of variation |
--min-fanout-sources | auto | Minimum internal sources for C2 fanout |
--min-portscan-ports | auto | Minimum unique ports for port scan |
--min-community-size | auto | Minimum nodes for community detection |
--internal-networks | RFC 1918 | Internal network CIDRs |
--scoring-method | hbos | Anomaly scoring: hbos or iforest |
Anomaly Scoring Algorithms
All detections produce findings that are scored using one of two anomaly detection methods. When a detection has 5 or more candidate groups, statistical scoring is applied; otherwise, fixed severity thresholds are used.
HBOS (Histogram-Based Outlier Scoring)
The default scoring method. HBOS builds equal-width histograms for each feature dimension, then scores each observation based on how rare its bin is.
How it works:
- For each feature (e.g., coefficient of variation, connection count, byte ratio), divide the observed range into 10 equal-width bins
- Count the number of observations in each bin to compute density:
density = count_in_bin / total_observations - Apply a floor to prevent log(0):
density = max(density, 0.5 / total) - Compute per-feature score:
score = -log10(density)— rarer bins produce higher scores - Sum all feature scores for the final anomaly score
Properties:
- Assumes feature independence (no cross-feature correlations)
- O(n) time complexity — fast, single-pass histogram construction
- Supports feature inversion for metrics where lower values are more suspicious (e.g., beacon CV where 0.01 is more suspicious than 0.5)
Isolation Forest (iForest)
An ensemble method that isolates anomalies using random partitioning trees.
How it works:
- Build 100 random isolation trees, each sampling 256 data points
- Each tree recursively partitions data by randomly selecting a feature and split value until each point is isolated (or max depth is reached)
- For each observation, compute the average path length across all trees — anomalies are isolated in fewer splits
- Convert to anomaly score:
score = 2^(-avg_path_length / c(n))wherec(n)is the expected path length for a balanced BST - Transform to match HBOS scale:
final_score = -log10(1 - raw_score)
Properties:
- Captures cross-feature interactions (unlike HBOS)
- More robust to feature scaling
- Higher computational cost than HBOS
- Deterministic (seed = 42 for reproducibility)
Select scoring method:
rockfish hunt -d /data --sensor my-sensor --scoring-method iforest
Severity Mapping
Anomaly scores are mapped to severity levels using percentile-based thresholds across the entire finding population:
| Percentile | Severity |
|---|---|
| ≥ 95th | Critical |
| ≥ 85th | High |
| ≥ 70th | Medium |
| < 70th | Low |
If the maximum score across all findings is below 2.0, severity is capped at Medium to suppress false positives in benign environments.
Detection Algorithm Details
Beaconing
Detects C2 callbacks by measuring the regularity of connection intervals.
- Group connections by
(src_ip, dest_ip, dest_port) - Compute inter-arrival time intervals between consecutive connections
- Calculate the coefficient of variation:
CV = stddev / mean - A perfect beacon has CV ≈ 0; random traffic has CV ≈ 1.0
Scoring features: CV (inverted), connection count, mean interval, byte consistency (CV of payload sizes)
| Threshold | Severity |
|---|---|
| CV < 0.05, connections > 50 | Critical |
| CV < 0.1 | High |
| CV ≤ 0.2 (max threshold) | Medium |
Lateral Movement
Detects multi-hop attack chains where internal hosts are progressively compromised.
- Build a temporal adjacency graph:
src → [(dest, timestamp)] - Identify pivot hosts (both source and destination)
- For each pivot, look for inbound → outbound sequences within a 1-hour window
- Extend chains recursively (up to 10 hops)
| Chain Length | Severity |
|---|---|
| ≥ 5 hops | Critical |
| ≥ 4 hops | High |
| ≥ 3 hops (minimum) | Medium |
C2 Fanout
Detects a single external IP receiving connections from many internal hosts (botnet controller pattern).
| Unique Sources | Severity |
|---|---|
| ≥ 20 internal hosts | Critical |
| ≥ 10 | High |
| ≥ 5 (minimum) | Medium |
Port Scan
Detects hosts probing many ports on a target.
- Count distinct destination ports per
(src_ip, dest_ip)pair - Detect sequential port runs (e.g., 80-84) and compute
sequential_ratio - Compute scan rate (ports per second)
Scoring features: unique ports, flow count, sequential ratio, scan rate
| Unique Ports | Severity |
|---|---|
| ≥ 100 | Critical |
| ≥ 50 | High |
| ≥ 25 (minimum) | Medium |
Community Detection
Identifies botnet-like clusters using Kosaraju’s Strongly Connected Components algorithm.
- Build a directed graph from flow data
- Find SCCs where every node can reach every other node
- Compute density:
edges / (n × (n-1))
| Community Size | Severity |
|---|---|
| ≥ 10 hosts | Critical |
| ≥ 5 | High |
| ≥ 3 (minimum) | Medium |
DNS Tunneling
Detects data exfiltration encoded in DNS subdomain labels.
- Pre-filter: average subdomain length must exceed 15 characters
- Analyze unique subdomain count, TXT record ratio, and query rate per base domain
Scoring features: unique subdomains, avg label length, TXT ratio, query rate
| Condition | Severity |
|---|---|
| ≥ 500 subdomains AND avg length ≥ 25 | Critical |
| ≥ 200 subdomains OR TXT ratio ≥ 0.5 | High |
| Meets pre-filter thresholds | Medium |
Data Exfiltration
Detects internal hosts uploading disproportionate data volumes to external hosts.
- Compute byte ratio:
bytes_out / (bytes_out + bytes_in)— ratio ≥ 0.8 is suspicious - Filter: minimum 10 MB outbound
Scoring features: total bytes out, byte ratio, flow count
| Condition | Severity |
|---|---|
| ≥ 1 GB AND ratio ≥ 0.95 | Critical |
| ≥ 100 MB | High |
| ≥ 10 MB, ratio ≥ 0.8 | Medium |
New Connection Pair
Detects (src_ip, dest_ip, dest_port) tuples never seen in the 7-day baseline window. Particularly important for OT/IoT networks where traffic is highly deterministic.
Known OT ports (Modbus 502, DNP3 20000, MQTT 1883/8883, BACnet 47808, EtherNet/IP 44818, S7comm 102, OPC UA 4840, IEC 104 2404) trigger elevated severity.
| Condition | Severity |
|---|---|
| OT port, flows ≥ 5 | Critical |
| OT port, any flows | High |
| Regular port, flows ≥ 10 | High |
| Otherwise | Medium |
Polling Disruption
Detects when previously periodic communication becomes irregular or stops entirely. Designed for SCADA/OT environments.
- Identify connections periodic in baseline (CV ≤ 0.3)
- Detect disruption: either stopped (0 recent flows) or irregular (recent CV > 0.8)
| Condition | Severity |
|---|---|
| Stopped, baseline > 100 flows | Critical |
| Stopped | High |
| Irregular, CV > 2.0 | High |
| Irregular | Medium |
Baseline Deviation
Detects significant deviations from historical traffic patterns.
- Compare recent (1 hour) vs baseline (7 days) for same connection tuples
- Compute ratios:
flow_ratio = recent / baseline,bytes_ratio = recent / baseline - Flag new protocols not seen in baseline
Scoring features: flow count ratio, bytes ratio, new protocol count
| Condition | Severity |
|---|---|
| Flow ratio > 10 OR ≥ 3 new protocols | Critical |
| Flow ratio > 5 OR bytes ratio > 5 OR ≥ 1 new protocol | High |
| Ratio > 2.0 | Medium |
Continuous Mode
# Run every hour (default)
rockfish hunt -d /data --sensor my-sensor --hive --continuous
# Run every 15 minutes
rockfish hunt -d /data --sensor my-sensor --hive \
--continuous --interval-minutes 15
Time Window
rockfish hunt -d /data --sensor my-sensor -t "24 hours" # default
rockfish hunt -d /data --sensor my-sensor -t "7 days"
rockfish hunt -d /data --sensor my-sensor -t "1 hour"
Examples
# Standard 24-hour threat hunt
rockfish hunt -d /data/rockfish --sensor prod-01 --hive -t "24 hours"
# Continuous with high severity filter
rockfish hunt -d /data --sensor prod-01 --hive \
--continuous --interval-minutes 30 --min-severity high
# Beaconing with custom thresholds
rockfish hunt -d /data --sensor my-sensor \
--detections beaconing --min-beacon-connections 50 --max-beacon-cv 0.15
rockfish alert
Publish detection events to MQTT, Kafka, and/or webhooks for automated response.
Overview
The alert command reads Suricata alert and hunt finding Parquet data, normalizes events into a common JSON payload, and publishes them to MQTT, Kafka, and/or webhook endpoints. It supports deduplication, rate limiting, confidence filtering, and continuous polling.
This is the “R” (Response) in NDR — enabling closed-loop automated response via n8n, Node-RED, Fluent Bit, Vector, or any downstream consumer.
Usage
rockfish alert [OPTIONS]
Alert Payload
All alerts are normalized to a common JSON schema:
{
"alert_id": "RF-2026-00042",
"timestamp": "2026-02-16T14:32:07Z",
"detection_type": "signature",
"confidence": 0.95,
"source": {
"ip": "10.0.12.45"
},
"destinations": [
{ "ip": "185.220.101.34", "port": 443 }
],
"metadata": {
"protocol": "TCP",
"suricata_sid": 2025001,
"suricata_signature": "ET MALWARE Cobalt Strike Beacon",
"suricata_category": "A Network Trojan was detected",
"community_id": "1:abc123"
},
"recommended_action": "block_ip"
}
Topic Mapping
MQTT Topics (forward slashes)
| Source | Topic |
|---|---|
| Suricata alert | rockfish/alerts/signature |
| Hunt: beaconing | rockfish/alerts/c2_beacon |
| Hunt: lateral movement | rockfish/alerts/lateral_movement |
| Hunt: exfiltration | rockfish/alerts/exfiltration |
| Hunt: DNS tunneling | rockfish/alerts/anomaly |
| Heartbeat | rockfish/status/heartbeat |
Kafka Topics (dots)
| Source | Topic |
|---|---|
| Suricata alert | rockfish.alerts.signature |
| Hunt: beaconing | rockfish.alerts.c2_beacon |
| Heartbeat | rockfish.status.heartbeat |
MQTT Options
| Option | Env Var | Default |
|---|---|---|
--mqtt-broker | MQTT_BROKER | localhost |
--mqtt-port | MQTT_PORT | 1883 |
--mqtt-client-id | MQTT_CLIENT_ID | rockfish-alert |
--mqtt-qos | MQTT_QOS | 1 |
--mqtt-topic-prefix | MQTT_TOPIC_PREFIX | rockfish |
--mqtt-username | MQTT_USERNAME | — |
--mqtt-password | MQTT_PASSWORD | — |
--mqtt-tls | MQTT_TLS_ENABLED | false |
Kafka Options
Requires building with the
kafkafeature.
| Option | Env Var | Default |
|---|---|---|
--kafka | KAFKA_ENABLED | false |
--kafka-brokers | KAFKA_BROKERS | localhost:9092 |
--kafka-topic-prefix | KAFKA_TOPIC_PREFIX | rockfish |
--kafka-client-id | KAFKA_CLIENT_ID | rockfish-alert |
--kafka-username | KAFKA_USERNAME | — |
--kafka-password | KAFKA_PASSWORD | — |
--kafka-security-protocol | KAFKA_SECURITY_PROTOCOL | plaintext |
--kafka-compression | KAFKA_COMPRESSION | none |
Supported security protocols: plaintext, ssl, sasl_plaintext, sasl_ssl
Supported compression: none, gzip, snappy, lz4, zstd
Webhook Options
Requires building with the
webhookfeature.
| Option | Env Var | Default |
|---|---|---|
--webhook | WEBHOOK_ENABLED | false |
--webhook-url | WEBHOOK_URL | — |
--webhook-secret | WEBHOOK_SECRET | — |
--webhook-timeout | WEBHOOK_TIMEOUT | 10 |
Multiple webhook URLs can be specified as a comma-separated list. When a secret
is configured, requests are signed with HMAC-SHA256 via the X-Rockfish-Signature
header. The webhook publisher includes automatic retry with exponential backoff
and deduplication.
Confidence Mapping
Suricata severity:
| Severity | Confidence |
|---|---|
| 1 | 0.95 |
| 2 | 0.85 |
| 3 | 0.70 |
| 4+ | 0.50 |
Hunt severity:
| Severity | Confidence |
|---|---|
| critical | 0.95 |
| high | 0.85 |
| medium | 0.70 |
| low | 0.55 |
Deduplication
Identical alerts (same source IP, destination IP, detection type, and SID) are suppressed within a configurable time window (default: 5 minutes).
YAML Configuration
alert:
mqtt:
broker: mosquitto
port: 1883
client_id: rockfish-alert
qos: 1
topic_prefix: rockfish
kafka:
brokers: "kafka1:9092,kafka2:9092"
topic_prefix: rockfish
security_protocol: sasl_ssl
compression: snappy
webhook:
urls:
- https://hooks.example.com/alert
secret: my-webhook-secret
timeout_secs: 10
confidence_threshold: 0.75
poll_interval_secs: 30
dedup_window_secs: 300
enabled_types:
- signature
- lateral_movement
- c2_beacon
Heartbeat
Periodic heartbeat published to {prefix}/status/heartbeat:
{
"timestamp": "2026-02-16T14:32:07Z",
"uptime_secs": 3600,
"alerts_published": 142,
"status": "running"
}
Examples
# Single-shot MQTT publish
rockfish alert -d /data --sensor my-sensor --hive \
--mqtt-broker mosquitto -t "1 hour"
# Continuous MQTT + Kafka publishing
rockfish alert -d /data --sensor my-sensor --hive \
--mqtt-broker mosquitto \
--kafka --kafka-brokers kafka1:9092,kafka2:9092 \
--continuous
# High-confidence only with TLS
rockfish alert -d /data --sensor prod-01 --hive \
--mqtt-broker mqtt.internal --mqtt-tls \
--confidence-threshold 0.85 --continuous
# Webhook delivery with HMAC signing
rockfish alert -d /data --sensor prod-01 --hive \
--webhook --webhook-url https://hooks.example.com/alert \
--webhook-secret my-secret --continuous
Fluent Bit Integration
Fluent Bit can subscribe to Rockfish alert topics via its MQTT input plugin and forward alerts to any supported output (Elasticsearch, Splunk, S3, HTTP, etc.).
[INPUT]
Name mqtt
Tag rockfish.alerts
Listen 0.0.0.0
Port 1883
[FILTER]
Name parser
Match rockfish.alerts
Key_Name payload
Parser json
[OUTPUT]
Name es
Match rockfish.alerts
Host elasticsearch
Port 9200
Index rockfish-alerts
Type _doc
Alternatively, use the Fluent Bit MQTT output to republish filtered or enriched alerts to a different broker:
[OUTPUT]
Name mqtt
Match rockfish.alerts
Host mqtt-central.example.com
Port 1883
Topic soc/alerts/rockfish
Note: Fluent Bit’s MQTT input runs an embedded MQTT server. Configure Rockfish to publish to the Fluent Bit listen address instead of a standalone broker, or use a shared MQTT broker (e.g., Mosquitto) with Fluent Bit subscribing as a client via a custom Lua script or the
mqttinput in listen mode.
n8n Integration
Subscribe to MQTT topics in n8n for automated response:
- Add an MQTT Trigger node subscribing to
rockfish/alerts/# - Parse the alert JSON payload
- Route by
detection_type - Execute response actions (block IP, quarantine host, create ticket)
rockfish mcp
Start an MCP (Model Context Protocol) server for AI-powered queries on Parquet data.
Overview
The MCP server exposes Parquet data to AI assistants and LLM toolchains using the Model Context Protocol. It provides query tools for data exploration and hunt tools for threat detection.
Usage
rockfish mcp [OPTIONS]
Transport Modes
| Mode | Use Case |
|---|---|
stdio (default) | Claude Desktop, local tool integration |
http | Web clients, remote access |
# stdio mode (default)
rockfish mcp
# HTTP mode
rockfish mcp -t http --host 0.0.0.0 --port 3000
Built-in Tools
Query Tools
| Tool | Description |
|---|---|
query | Query with SQL filters and column selection |
aggregate | Group and aggregate data |
sample | Get random sample rows |
count | Count rows with optional filter |
schema | Get column names and types |
list_sources | List configured data sources |
Hunt Tools
| Tool | Description |
|---|---|
detect_beaconing | Find C2 beacon patterns |
detect_lateral_movement | Trace internal attack chains |
detect_c2_fanout | Identify C2 fan-out patterns |
detect_port_scan | Find port scanning activity |
detect_communities | Discover botnet-like clusters |
detect_dns_tunneling | Flag DNS tunneling indicators |
detect_data_exfiltration | Find data exfiltration patterns |
Options
| Option | Default | Description |
|---|---|---|
-t, --transport | stdio | Transport mode: stdio or http |
--data-dir | from config | Override data directory |
--sensor | from config | Override sensor name |
--no-hive | — | Disable hive partitioning |
--host | 127.0.0.1 | HTTP server host |
--port | 3000 | HTTP server port |
Authentication
HTTP mode supports JWT token authentication and OAuth2. See the MCP authentication documentation for configuration details.
Examples
# Start MCP server for Claude Desktop
rockfish mcp --data-dir /data/rockfish --sensor prod-01
# HTTP mode for web clients
rockfish mcp -t http --host 0.0.0.0 --port 3000 \
--data-dir /data/rockfish --sensor prod-01
rockfish chat
Start an AI-powered chat server for conversational network security analysis.
Overview
The chat server provides a web-based conversational interface for querying network security data using natural language. It integrates with MCP for live data queries and supports pluggable LLM backends.
Usage
rockfish chat [OPTIONS]
LLM Modes
| Mode | Description |
|---|---|
slm (default) | Local small language model via Ollama |
cloud | Cloud LLM (OpenAI, Anthropic) |
hybrid | Try local SLM first, fall back to cloud |
# Local SLM mode
rockfish chat --mode slm
# Cloud mode
rockfish chat --mode cloud
# Hybrid mode
rockfish chat --mode hybrid
Data Modes
| Mode | Description |
|---|---|
cache (default) | Query local pre-filtered Parquet files |
store | Query via MCP cold storage |
Options
| Option | Default | Description |
|---|---|---|
-c, --config | — | Chat configuration file (chat.yaml) |
--host | 127.0.0.1 | HTTP server host |
--port | 8082 | HTTP server port |
--mode | slm | LLM mode: slm, cloud, hybrid |
--data-mode | cache | Data mode: cache or store |
--mcp-endpoint | http://localhost:3000 | MCP server endpoint |
--slm-endpoint | http://localhost:8081/v1/chat/completions | Local SLM endpoint |
--data-dir | ./output | Data directory for cache mode |
--sensor | sensor | Sensor name for cache mode |
Features
- Session management with state persistence
- Security guardrails and response caching
- MCP integration for live data queries
- Natural language to SQL translation
Examples
# Start chat server with local SLM
rockfish chat --mode slm \
--data-dir /data/rockfish --sensor prod-01
# Start with MCP integration
rockfish chat --mode cloud \
--mcp-endpoint http://localhost:3000 \
--host 0.0.0.0 --port 8082
rockfish update
Download and install Suricata rule updates. Functionally equivalent to suricata-update.
Overview
The update command manages Suricata rule sources — downloading rule archives
(ET Open by default), applying local filter files (enable, disable, drop, modify),
and writing a merged suricata.rules file. Optionally reloads Suricata after a
successful update.
Usage
# Download rules, apply filters, write suricata.rules
rockfish update
# Force re-download and reload Suricata
rockfish update --force --reload
# Use ET Pro rules (requires oinkcode)
rockfish update --etpro YOUR_OINKCODE
# Include additional local rules
rockfish update --local /etc/suricata/rules/local.rules,/etc/suricata/rules/custom/
Options
| Option | Default | Description |
|---|---|---|
--suricata-version | auto-detect | Suricata version (e.g. 7.0) |
--data-dir | /var/lib/suricata | Cache directory for downloaded archives and source state |
--config-dir | /etc/suricata | Directory containing filter files |
--output | /var/lib/suricata/rules/suricata.rules | Output path for merged rules |
--etpro | - | Proofpoint ET Pro oinkcode |
--url | - | Custom URL for the primary rule source |
--local | - | Additional local .rules files or directories (comma-separated) |
--reload | false | Reload Suricata after update (via suricatasc) |
--no-reload | false | Explicitly disable reload |
--force | false | Force re-download even if cache is fresh |
Source Management
List available sources
rockfish update list-sources
Fetches the OISF source index and displays all available rule sources with their enabled/disabled status.
Enable a source
rockfish update enable-source et/pro
rockfish update enable-source oisf/trafficid
Disable a source
rockfish update disable-source et/pro
Remove a source
Disables the source and deletes its cached archive:
rockfish update remove-source et/pro
Refresh the source index
rockfish update update-sources
Filter Files
Filter files control which rules are enabled, disabled, converted to drop, or modified.
Place them in the config directory (/etc/suricata/ by default).
enable.conf
Force-enable rules matching the specified patterns:
# By SID
2100001
# By SID range
2100001-2100010
# By regex on rule text
re:ET SCAN
# By group (source filename)
group:emerging-scan.rules
disable.conf
Disable rules matching the specified patterns (same format as enable.conf):
# Disable noisy rules
re:ET INFO
2100498
group:emerging-deleted.rules
drop.conf
Change the action to drop for matching rules (same format):
# Drop all exploit rules
re:ET EXPLOIT
modify.conf
Regex find-and-replace on matching rules:
# Change action from alert to drop for specific SID
2100001 "alert" "drop"
# Change action for all SCAN rules
re:ET SCAN "alert" "drop"
YAML Configuration
Settings can also be specified in rockfish.yaml:
update:
suricata_version: "7.0"
data_dir: /var/lib/suricata
config_dir: /etc/suricata
output: /var/lib/suricata/rules/suricata.rules
reload: true
etpro_code: "YOUR_OINKCODE"
CLI arguments override YAML settings.
Pipeline
The update command follows this pipeline:
- Resolve Suricata version (auto-detect via
suricata -Vor--suricata-version) - Download rule archives for each enabled source (ET Open by default)
- Extract
.rulesfiles from tar.gz archives - Load additional local rule files (if
--localis specified) - Parse all rules, extracting SIDs and group metadata
- Apply filters in order: enable.conf → disable.conf → drop.conf → modify.conf
- Write merged, deduplicated
suricata.rulessorted by SID - Reload Suricata (if
--reloadis set) viasuricatasc -c reload-rules
Reload Behavior
When --reload is specified, Rockfish attempts to reload Suricata rules:
- First tries
suricatasc -c reload-rules - Falls back to sending
SIGUSR2to the Suricata process (found via pidfile orpidof)
Default Rule Source
Without any additional sources enabled, Rockfish downloads the Emerging Threats Open ruleset:
https://rules.emergingthreats.net/open/suricata-{version}/emerging.rules.tar.gz
When an ET Pro oinkcode is provided (--etpro), ET Pro replaces ET Open:
https://rules.emergingthreatspro.com/{code}/suricata-{version}/etpro.rules.tar.gz
Examples
# Basic update with ET Open rules
rockfish update
# ET Pro with reload
rockfish update --etpro abc123 --reload
# Custom paths
rockfish update \
--data-dir /opt/suricata/data \
--config-dir /opt/suricata/etc \
--output /opt/suricata/rules/suricata.rules
# Include local rules alongside downloaded rules
rockfish update --local /etc/suricata/rules/local.rules
# Force fresh download
rockfish update --force
# Automated cron job
rockfish update --reload --quiet
Cron Example
Run rule updates every 6 hours and reload Suricata:
0 */6 * * * /opt/rockfish/bin/rockfish update --reload --quiet 2>&1 | logger -t rockfish-update
rockfish prune
Remove old Parquet files based on a retention policy.
Overview
The prune command enforces data retention by removing Parquet files older
than a configurable window. Supports dry-run preview and per-event-type
granularity.
Usage
rockfish prune [OPTIONS]
Options
| Option | Default | Description |
|---|---|---|
-d, --data-dir | ./output | Data directory with Parquet files |
--sensor | sensor | Sensor name subdirectory |
--hive | — | Enable hive-style partitioning |
-r, --retention | 30d | Retention period |
--event-types | all | Event types to prune (comma-separated) |
--include-inventory | — | Also prune inventory files |
--dry-run | — | Preview without deleting |
Retention Periods
Supports various time formats:
rockfish prune -r "30d" # 30 days
rockfish prune -r "7d" # 7 days
rockfish prune -r "24h" # 24 hours
rockfish prune -r "30 days" # 30 days (verbose)
Examples
# Preview what would be deleted (dry run)
rockfish prune -d /data/rockfish --sensor prod-01 --hive \
-r "30d" --dry-run
# Delete files older than 7 days
rockfish prune -d /data/rockfish --sensor prod-01 --hive -r "7d"
# Prune only alert and flow data
rockfish prune -d /data --sensor prod-01 \
-r "14d" --event-types alert,flow
# Prune with inventory files
rockfish prune -d /data --sensor prod-01 \
-r "30d" --include-inventory
rockfish compact
Compact and merge Parquet files for storage efficiency.
Overview
The compact command merges multiple small Parquet files from recent days into larger, optimized files and removes data older than the configured retention period. This reduces file count, improves query performance, and manages disk usage.
Usage
rockfish compact [OPTIONS]
Options
| Option | Description | Default |
|---|---|---|
-d, --data-dir | Parquet data directory | required |
--sensor | Sensor name for partitioning | — |
--hive | Use hive-style date partitioning | false |
--retention | Data retention period | 30d |
--dry-run | Preview changes without modifying files | false |
Examples
# Compact and prune with 30-day retention
rockfish compact -d /data --sensor prod-01 --hive --retention 30d
# Preview what would be compacted
rockfish compact -d /data --sensor prod-01 --hive --dry-run
# 90-day retention
rockfish compact -d /data --sensor prod-01 --hive --retention 90d
How It Works
- Scans partitioned Parquet directories for small files
- Merges files from the same partition into larger, optimized files
- Removes partitions older than the retention period
- Preserves all data and metadata during compaction
rockfish config
Show resolved configuration, enabled features, and system information.
Overview
The config command displays the current configuration including YAML settings,
environment file values (with secrets masked), enabled compile-time features,
and license information.
Usage
rockfish config [OPTIONS]
Output
The command displays:
- Config file — resolved path to the active YAML configuration
- Environment file — loaded environment variables (secrets masked as
XXX...last8) - Enabled features — compile-time feature flags
- License information — tier, customer, expiration (if a license file is provided)
Examples
# Show all configuration
rockfish config
# With a specific config file
rockfish -c /etc/rockfish/rockfish.yaml config
# With license info
rockfish --license /etc/rockfish/license.json config
Feature Flags
The config output includes which features were compiled in:
Compile-time Features
---------------------
s3 enabled
mcp enabled
hunt enabled
report enabled
chat enabled
alert enabled
kafka disabled
geoip enabled
ip_reputation enabled
Enrichment
Rockfish NDR enriches flow data with geographic and reputation intelligence during ingestion.
GeoIP
Geographic location lookups via MaxMind GeoLite2 or GeoIP2 databases.
Enriched Fields
| Field | Description |
|---|---|
dest_country | Destination country (ISO 3166) |
dest_city | Destination city name |
dest_as_org | Destination ASN organization |
dest_asn | Destination ASN number |
dest_latitude | Destination latitude |
dest_longitude | Destination longitude |
Configuration
enrichment:
geoip:
database_path: /usr/share/GeoIP/GeoLite2-City.mmdb
asn_database_path: /usr/share/GeoIP/GeoLite2-ASN.mmdb
Requirements
- MaxMind GeoLite2-City and GeoLite2-ASN databases
- Free account at maxmind.com
- Requires the
geoipfeature (enabled by default) - Available at all license tiers (GeoIP country/ASN enrichment is included from Free)
IP Reputation
Abuse confidence scoring via the AbuseIPDB API.
Enriched Fields
| Field | Description |
|---|---|
drep | Destination abuse confidence score (0-100) |
drep_reports | Number of abuse reports |
drep_isp | ISP/hosting provider |
drep_domain | Domain associated with the IP |
Configuration
enrichment:
ip_reputation:
enabled: true
api_key: ${ABUSEIPDB_API_KEY}
cache_path: /var/lib/rockfish/ip_cache.parquet
cache_ttl_hours: 48
memory_cache_size: 50000
lookup_timeout_ms: 200
fail_open: false
Caching
IP reputation lookups are cached at two levels:
- Memory cache — LRU cache (default: 50,000 entries) for fast lookups
- Parquet cache — Persistent disk cache with configurable TTL
Requirements
- AbuseIPDB API key (set in environment file)
- Requires the
ip_reputationfeature (enabled by default) - Requires the Basic license tier or higher
Report Integration
Both GeoIP and IP reputation data appear across multiple report pages:
- Overview — Top countries by flow volume
- Flows — Country breakdown with GeoIP data
- Threats — IP reputation scores and flagged hosts
- Network — Node detail panel with geographic info
- World Map — Leaflet.js globe with country-level overlays
Note: GeoIP and IP reputation columns are only populated when the probe runs with those features enabled. Report queries gracefully return zero rows when enrichment data is absent.
Asset Inventory
Passive device discovery from observed network traffic.
Overview
Rockfish builds an asset inventory by analyzing network flow patterns, extracting DHCP metadata, and inferring device roles — all without agents or active scanning.
Capabilities
| Feature | Description |
|---|---|
| IP Tracking | All observed IPs with communication patterns and protocol usage |
| DHCP Metadata | MAC address, hostname, vendor class ID extraction |
| Device Role Inference | Automatic classification based on traffic patterns |
| New Device Detection | Flags IPs not present in baseline |
| OT Protocol Awareness | Identifies industrial protocol usage |
| Inventory Snapshots | Periodic snapshots written to Parquet |
Inferred Device Roles
| Role | Detection Criteria |
|---|---|
| PLC | Modbus, DNP3, EtherNet/IP, or S7comm traffic |
| HMI | Mixed OT and standard protocols |
| Sensor | Read-only OT protocol patterns |
| Engineering Workstation | OT + administrative protocols |
| Server | Listening on well-known ports |
| Client | Outbound-initiated connections |
OT Protocol Support
| Protocol | Description |
|---|---|
| Modbus | Industrial serial communication |
| DNP3 | Distributed Network Protocol |
| MQTT | IoT message queuing |
| BACnet | Building automation |
| EtherNet/IP | Industrial Ethernet |
| S7comm | Siemens S7 communication |
| OPC UA | Open Platform Communications |
| IEC 104 | Telecontrol protocols |
Report Integration
The Inventory report page displays:
- Device list with inferred roles and protocol usage
- New/unknown device alerts
- OT protocol traffic summary
- First-seen and last-seen timestamps
- Communication pattern metrics (connection count, bytes)
ML-Based Analytics
Rockfish NDR ships four detection mechanisms that all fall under the ML-based analytics umbrella: iForest, HBOS, OCCAM, and OCCAM. Each catches threats the others miss, and the real value comes from running them together as a layered pipeline.
This page covers what each one is, how they layer, and where each shows up in the report.
The four mechanisms at a glance
| iForest | HBOS | OCCAM | OCCAM | |
|---|---|---|---|---|
| Kind | ML algorithm | Statistical algorithm | Detection engine | Detection engine |
| Built on | — | — | Uses HBOS | Uses OCCAM tokens |
| Granularity | Per flow | Per asset / window | Per asset / 15-min window | Per token observation |
| Output | Anomaly score (0..1) | Outlier score (sum of −log P(feature)) | Token + ATT&CK tactic + surprisal in bits | Disposition (suppressed / investigate / present / elevated) + Viterbi path |
| Trained | Offline (historical data) | Online (Welford streaming) | Online (HBOS internally) | Online (HMM over token vocabulary) |
| Where it shows up | Anomalies page | Reliability page | OCCAM page | Occam page |
iForest and HBOS are math — algorithms that compute anomaly scores from a feature vector. OCCAM and OCCAM are engines — pipelines that use those algorithms and add structure (per-asset windows, ATT&CK mapping, sequence modeling) to produce security-meaningful output.
Why all four ship together — the layering effect
Four engines. One layered prediction.
Each stage answers a question the layer above couldn't. Data flows left to right — from a single flow's anomaly score to a predictive alert about an in-progress attack.
Reading the diagram
| Stage | Question it answers | Output | Where it surfaces |
|---|---|---|---|
| iForest | Is THIS flow anomalous? | per-flow score | Anomalies page |
| HBOS | Did this HOST drift from itself? | drift events, SLA breaches | Reliability page |
| OCCAM | Is the drift security-meaningful? | ATT&CK-mapped tokens | OCCAM page |
| OCCAM | Does the sequence match an attack? | dispositions, pre-intrusion alerts | Occam page |
Each layer answers a question the layer above can’t:
- iForest catches the first-pass per-flow weirdness you’d otherwise miss entirely (encrypted threats, novel tooling, low-frequency C2).
- HBOS turns that weirdness into per-host context — “this happened repeatedly for asset X” — and additionally catches gradual host degradation (network reliability) for free.
- OCCAM turns that context into a security narrative mapped to the MITRE ATT&CK matrix — “X is showing exfiltration-tactic behavior” — so analysts can triage by attacker intent, not by mystery score.
- OCCAM turns the narrative into a predictive alert by recognizing multi-stage attack paths — “X is mid-attack-path; investigate now” — and suppresses isolated tokens that don’t match any known sequence.
Skipping a layer leaves a gap:
| Skip | Cost |
|---|---|
| Skip iForest | Blind to flows your IDS rules don’t already know about |
| Skip HBOS | No per-host context; performance and security teams duplicate work |
| Skip OCCAM | Analysts stuck interpreting raw anomaly scores with no security framing |
| Skip OCCAM | One-shot anomalies overwhelm the SOC; real attacks reach completion before response |
What each one is
iForest — Isolation Forest
An unsupervised ML algorithm that builds many random trees that partition the feature space; anomalies are easier to “isolate” and produce shorter average path lengths in the trees.
- Pre-trained model — Rockfish ships baseline iForest models trained on representative traffic, and supports retraining against your own data.
- Strengths: handles high-dimensional input, no distribution assumptions, works on encrypted traffic where signatures don’t.
- Weaknesses: opaque (“why is this anomalous?”), needs periodic retraining as your network evolves.
- In the report: the Anomalies page surfaces top per-flow scores.
HBOS — Histogram-Based Outlier Score
A 2012 statistical anomaly score (Goldstein, Dengel). Per-feature
histograms give the probability of seeing each value; the outlier score is
the sum of −log P(feature_i) across features. High score = the
combination of feature values is rare for this asset.
Rockfish runs HBOS-style baselines online using Welford’s algorithm so each asset has its own per-feature mean / variance estimate updated incrementally.
- Strengths: fast, online (no training data needed), interpretable per-feature, runs cheaply on every asset every 15 minutes.
- Weaknesses: assumes feature independence; less powerful than tree-based methods on highly correlated features.
- In the report: the Reliability page surfaces drift events and SLA breaches; HBOS also feeds the OCCAM classifier.
OCCAM — Behavioral tokenizer
Naming caveat: Rockfish OCCAM is not the OCCAM-rules format from sigmahq.io. They share a name but are unrelated. Rockfish OCCAM is a behavioral tokenizer; the public OCCAM is a YAML detection-rule grammar.
Every 15 minutes, for each asset, OCCAM aggregates window stats and
emits tokens — short labels representing observed behavior categories
(e.g. encrypted-ratio-high, slow-handshake, outbound-spike,
unusual-port-mix). Each token carries:
-
A surprisal score in bits — how rare this token is for this asset, computed via HBOS on the underlying features.
-
An ATT&CK tactic mapping — initial access, execution, lateral movement, exfiltration, etc. — so the token is security-meaningful.
-
Strengths: translates raw statistics into a vocabulary an analyst recognizes; per-asset baselining makes “rare for this host” the standard.
-
Weaknesses: single-window view; can’t see “lead-up” patterns by itself.
-
In the report: the OCCAM page surfaces tokens, dispositions, and the per-tactic breakdown.
OCCAM — Sequence predictor
Named for Occam’s razor: when multiple attack paths could explain the observed token sequence, pick the simplest. OCCAM is a Hidden Markov Model trained on historical token sequences leading up to confirmed incidents. It runs the Viterbi algorithm over the live OCCAM token stream to score how strongly the recent sequence resembles a known attack path.
Output is a per-token disposition:
| Disposition | Meaning |
|---|---|
suppressed | Isolated anomaly, no matching sequence — likely false positive |
investigate | Anomaly worth looking at but not part of a known path |
present | Token participates in a partial attack path |
elevated | Token completes or extends a recognized attack path — pre-intrusion alert |
- Strengths: forecasts attacks while they’re still in progress; cuts false positives via sequence context; explainable Viterbi paths.
- Weaknesses: depends on OCCAM tokens flowing; HMM trained on labeled attack paths.
- In the report: the Occam page surfaces dispositions, Viterbi paths, and pre-intrusion alerts.
Concrete example — the same incident through four lenses
A workstation slowly exfiltrating encrypted data over hours:
| Layer | What it produces | How an analyst reads it |
|---|---|---|
| iForest | Flow 10.0.12.45 → 198.51.100.47:443 scored 0.87 | “This single flow’s overall feature pattern resembles known-bad more than known-good.” |
| HBOS | Asset 10.0.12.45 window 14:00–14:15 has tcp_handshake_rtt_ms_avg 4.2σ above its baseline | “This host’s measurements drifted from its own history.” |
| OCCAM | Same window emits token encrypted-ratio-high · tactic=exfiltration · surprisal=8.7 bits | “The drift is behaviorally meaningful and maps to the exfiltration tactic.” |
| OCCAM | Disposition elevated; Viterbi path = recon → discovery → encrypted-ratio-high | “And the recent token sequence matches the exfiltration HMM. Investigate now.” |
Each layer answers a question the layer above couldn’t:
| Layer | Answers |
|---|---|
| iForest | Is this single flow anomalous? |
| HBOS | Did this host’s measurements deviate from itself? |
| OCCAM | Is the deviation behaviorally meaningful? |
| OCCAM | Does the recent sequence look like a known attack path? |
When to look at each
| Question / situation | Use |
|---|---|
| Score one flow in isolation | iForest |
| Detect host degradation or compromise at the network-quality level | HBOS via Reliability page |
| Triage anomalies by ATT&CK tactic | OCCAM |
| Forecast an incident before it completes | OCCAM |
| Suppress noisy false positives | OCCAM |
License tier
The Anomaly lens (iForest / HBOS) and the Behavioral lens (OCCAM HMM sequence predictor) are Professional-tier features — both gate at Professional+ and on the corresponding parquet datasets being present. The detection swimlane + topology graph that overlay these lenses are Enterprise-tier. See License Tiers for the full feature matrix.
Deployment Profiles
Rockfish NDR supports different deployment profiles optimized for specific environments.
IT Profile
Enterprise network monitoring with full enrichment capabilities.
| Component | Configuration |
|---|---|
| GeoIP | Enabled — MaxMind GeoLite2 databases |
| IP Reputation | Enabled — AbuseIPDB API integration |
| S3 Upload | Enabled — cloud storage for retention |
| Chat | Cloud LLM (OpenAI, Anthropic) |
| Hunt | Full detection suite |
| Alert | MQTT/Kafka for SIEM integration |
# IT deployment example
enrichment:
geoip:
database_path: /usr/share/GeoIP/GeoLite2-City.mmdb
ip_reputation:
enabled: true
api_key: ${ABUSEIPDB_API_KEY}
s3:
bucket: security-data
region: us-east-1
alert:
mqtt:
broker: siem-mqtt.internal
OT Profile
Industrial / IoT environments with asset inventory and baseline monitoring.
| Component | Configuration |
|---|---|
| GeoIP | Optional |
| IP Reputation | Optional |
| S3 Upload | Optional — local storage preferred |
| Chat | Local SLM (air-gap compatible) |
| Hunt | Baseline deviation, polling disruption |
| Inventory | OT protocol awareness enabled |
# OT deployment example
hunt:
detections: "baseline_deviation,polling_disruption,beaconing,lateral"
internal_networks: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
Key OT detections:
- Baseline deviation — traffic pattern shifts in control networks
- Polling disruption — interruption of periodic SCADA polling
- New connection pairs — unexpected host communication
Military Profile
Air-gapped networks with no internet dependencies.
| Component | Configuration |
|---|---|
| GeoIP | Offline databases only |
| IP Reputation | Disabled — no API access |
| S3 Upload | Disabled — local storage only |
| Chat | Local SLM only (Ollama) |
| Hunt | Data exfiltration focus |
| Alert | Local MQTT broker only |
# Air-gapped deployment example
enrichment:
geoip:
database_path: /opt/rockfish/geoip/GeoLite2-City.mmdb
ip_reputation:
enabled: false
hunt:
detections: "exfiltration,beaconing,lateral,fanout,dns_tunneling"
alert:
mqtt:
broker: localhost
Key considerations:
- All enrichment data must be pre-loaded locally
- SLM (small language model) runs entirely on-device
- No S3, no cloud APIs, no external network access
- Focus on data exfiltration and unauthorized communication detection
Docker Deployment
Rockfish NDR provides Docker images for containerized deployment.
Production Container
# Build
docker build -t rockfish:latest -f Dockerfile .
# Run with mounted config and data
docker run -d --name rockfish \
-v /opt/rockfish/etc:/opt/rockfish/etc:ro \
-v /data/rockfish:/data/rockfish \
-p 3000:3000 -p 8082:8082 \
rockfish:latest ingest --socket /var/run/suricata/eve.sock
The production image is a multi-stage build (Rust builder, Debian slim runtime) and includes all default features plus Kafka, with DuckDB bundled from source.
Demo Container
# Build and run demo report on port 8080
docker build -t rockfish-demo:latest -f Dockerfile.demo .
docker run -d --name rockfish-demo -p 8080:8080 rockfish-demo:latest
The demo image generates a synthetic report at build time and serves it via nginx.
License Tiers
Detailed feature matrix for Rockfish NDR license tiers. The methods on
NdrTier in ndr/rockfish/crates/rockfish-types/src/license.rs are the
source of truth; this page mirrors them.
Free, Basic, and Professional are sold per sensor (one license per Suricata instance). Enterprise is a site license covering an unlimited number of sensors at a single site. See the Rockfish Portal for current pricing.
Tier Comparison
| Feature | Free | Basic | Professional | Enterprise |
|---|---|---|---|---|
| Events/min | 10,000 | 25,000 | 100,000 | Unlimited |
| Segments | 1 | 1 | 8 | Unlimited |
| Licensing scope | 1 sensor | 1 sensor | 1 sensor | Site (unlimited sensors) |
| Core panels (Overview, Alerts, Applications, DNS, TLS, Hosts, Flows) | Yes | Yes | Yes | Yes |
| OT protocol decoders | Yes | Yes | Yes | Yes |
| S3 backhaul / Parquet export | Yes | Yes | Yes | Yes |
| GeoIP country/ASN enrichment + world map | Yes | Yes | Yes | Yes |
| NIST PQC compliance (TLS) | Yes | Yes | Yes | Yes |
| Encrypted Traffic Analytics (ETA) | Yes | Yes | Yes | Yes |
| Performance lens (odometry) | Yes | Yes | Yes | Yes |
| Transport performance (per-flow) | Yes | Yes | Yes | Yes |
| IP reputation (AbuseIPDB) | — | Yes | Yes | Yes |
| Anomaly lens (iForest / HBOS) | — | — | Yes | Yes |
| Behavioral lens (SIGMA tactics + OCCAM HMM) | — | — | Yes | Yes |
| Hunt detection (beaconing, lateral, fanout, portscan, community) | — | — | Yes | Yes |
| Asset Inventory page | — | — | Yes | Yes |
| OT Protocol Traffic panel | — | — | Yes | Yes |
| Per-segment sub-reports | — | — | Yes | Yes |
| Parquet signing | — | — | Yes | Yes |
| Webhook publishing | — | — | Yes | Yes |
| Detection swimlane + topology graph | — | — | — | Yes |
| AI Assessment | — | — | — | Yes |
| MCP + Chat | — | — | — | Yes |
| Custom theme + logo (white-labelling) | — | — | — | Yes |
| MQTT / Kafka | — | — | — | Yes |
| External threat-intel feeds | — | — | — | Yes |
Several capabilities (PQC, ETA, Performance, Transport performance) are tier-permissive but data-strict: available at every tier, but the corresponding page or section only renders when the relevant Suricata plugin or processor has actually produced Parquet on disk.
The unified Detections page (combined-risk table + co-firing swimlane) is suppressed when fewer than two detection lenses are active, since it degrades to a worse Alerts view.
Free
The default when no license file is present. Provides core NDR functionality at 10,000 events/min over a single segment, one sensor:
- Core panels, OT protocol decoders, S3 backhaul
- GeoIP country/ASN enrichment + world map
- NIST PQC compliance, Encrypted Traffic Analytics
- Performance lens and per-flow Transport performance
The CLI auto-bumps Free to Basic for the first 45 days from its build date as a try-before-you-buy window.
Limit: 10,000 events per minute, 1 segment, 1 sensor.
Basic
Everything in Free, plus:
- IP reputation scoring (AbuseIPDB)
Limit: 25,000 events per minute, 1 segment, 1 sensor.
Professional
Adds detection intelligence:
- Everything in Basic
- Anomaly lens (iForest / HBOS)
- Behavioral lens (SIGMA tactic rules + OCCAM HMM sequence predictor)
- Hunt detection (beaconing, lateral movement, fanout, portscan, community)
- Asset Inventory page and the OT Protocol Traffic panel
- Per-segment sub-reports, Parquet signing, webhook publishing
Limit: 100,000 events per minute, up to 8 segments, 1 sensor.
Enterprise
Full NDR capability as a single-site license covering unlimited sensors:
- Everything in Professional
- Detection swimlane + network topology graph
- AI Assessment (LLM-generated executive summary)
- MCP server + in-report Chat
- Custom theme + logo, MQTT/Kafka, external threat-intel feeds
No event-rate or segment limit. Unlimited sensors at a single site.
License File Format
{
"id": "rockfish_acme-corp-enterprise_Abc123",
"tier": "enterprise",
"customer_name": "Acme Corp",
"customer_email": "[email protected]",
"max_events_per_min": null,
"issued_at": "2026-01-01T00:00:00Z",
"expires_at": "2027-01-01T00:00:00Z",
"signature": "base64-encoded-ed25519-signature"
}
Licenses are verified using Ed25519 digital signatures with a public key embedded in the binary. License provenance metadata is included in every Parquet file.
Deployment Model
- Annual (per-year) licenses with a 30-day grace period past
expires_at; software maintenance included for the license term - Per-sensor for Free / Basic / Professional; site license for Enterprise
- Runs on your VPC or on-premise
- No telemetry or phone home
- Fully air-gap capable
Portal Overview
The Rockfish Portal is a self-service web application that combines the marketing website with license management. It provides:
- Marketing pages — product information, features, pricing
- Shop — dynamic pricing from Stripe with tier comparison
- Registration — passwordless email-based authentication (magic links)
- License management — purchase, download, and manage licenses
- Stripe integration — payment processing with webhook support
- License server integration — delegates license signing to the license server
Architecture
User → Portal (Axum) → Stripe (payments)
→ License Server (signing)
→ S3 (data persistence)
→ SMTP (email)
The portal is a single Rust binary (rockfish-portal) that serves both the static marketing site and the dynamic commerce functionality.
URL Structure
| Path | Description | Auth |
|---|---|---|
/ | Marketing landing page | Public |
/features.html | Feature overview | Public |
/shop | Dynamic pricing (from Stripe) | Public |
/enter | Email entry / login | Public |
/auth?token=... | Magic link authentication | Public |
/dashboard | License list | Logged in |
/dashboard/buy | Purchase a license | Logged in |
/checkout | Stripe checkout redirect | Logged in |
/webhook/stripe | Stripe payment webhook | Stripe |
/terms | Terms & Conditions | Public |
/privacy | Privacy Policy | Public |
Authentication
The portal uses passwordless magic link authentication:
- User enters email at
/enter - If email is new → account created automatically
- Magic link sent via SMTP
- User clicks link → session cookie set
- First-time users complete profile (name, company, accept terms)
- New users → redirected to Buy tab
- Returning users → redirected to Licenses tab
License Flow
- User selects a tier on the Buy tab
- Enters an Installation Name (min 5 characters, identifies the Suricata instance)
- Redirected to Stripe Checkout
- On payment confirmation:
- Stripe sends webhook to
/webhook/stripe - Portal asynchronously requests license from the license server
- License stored in DuckDB and synced to S3
- User can download or copy the license JSON from their dashboard
- Stripe sends webhook to
45-Day Professional Trial
Every issued license includes 45 days of Professional features from the issue date:
- The NDR engine checks
issued_atin the license - If within 45 days → grants Professional features regardless of tier (Enterprise features remain paid-only; an Enterprise license is never down-ranked)
- After 45 days → settles to purchased tier
- Re-checked once per day
(Separately, when no license file is present the CLI runs Free but auto-bumps to Basic for 45 days from its build date.)
Tiers
| Tier | Price | Events/min | Features |
|---|---|---|---|
| Basic | $0.99/yr | 25,000 | GeoIP, Parquet to S3 export, Reports |
| Professional | $99/yr | 100,000 | + IP Reputation, MCP |
| Enterprise | $999/yr | Unlimited | + OCCAM, Hunt, ML, Anomaly |
Prices are fetched dynamically from Stripe and cached for 8 hours.
Portal Control
PORTAL_DISABLED=true— shows “Coming Soon” page, allows pre-registration but disables purchasing- When disabled, users can still log in and view existing licenses
Data Persistence
The portal uses DuckDB locally and syncs to S3 after every write:
- New user registration
- Profile completion
- License issuance
On startup, data is loaded from S3 into local DuckDB. This supports ephemeral environments like DigitalOcean App Platform.
CLI Commands
# Run the portal server
rockfish-portal
# List registered users
rockfish-portal --list-users
# List licenses
rockfish-portal --list-licenses
# Verbose mode
rockfish-portal -v
Portal Deployment
Building
cd /development/rockfish/ndr
# Build Docker image
./scripts/build-portal.sh
# Push to DigitalOcean Container Registry
./scripts/build-portal.sh --push
# Build local binary
./scripts/build-portal.sh --install
Environment Variables
All configuration is via environment variables (.env file or platform settings).
Required
| Variable | Description |
|---|---|
PORTAL_DOMAIN | Public URL (e.g., https://portal.rockfishndr.com) |
STRIPE_SECRET_KEY | Stripe API secret key (sk_test_... or sk_live_...) |
STRIPE_WEBHOOK_SECRET | Stripe webhook signing secret (whsec_...) |
STRIPE_PRICE_BASIC | Stripe Product or Price ID for Basic tier |
STRIPE_PRICE_PROFESSIONAL | Stripe Product or Price ID for Professional tier |
STRIPE_PRICE_ENTERPRISE | Stripe Product or Price ID for Enterprise tier |
LICENSE_SERVER_TOKEN | Bearer token for the license server |
Optional
| Variable | Default | Description |
|---|---|---|
BIND_ADDR | 0.0.0.0:8080 | Server bind address |
DATABASE_PATH | /var/lib/rockfish/portal.db | DuckDB file path |
LICENSE_SERVER_HOST | http://127.0.0.1:8080 | License server URL |
SMTP_HOST | — | SMTP server (emails logged if unset) |
SMTP_PORT | 587 | SMTP port (587=STARTTLS, 465=TLS, 25/1025=plain) |
SMTP_USER | — | SMTP username |
SMTP_PASSWORD | — | SMTP password |
SMTP_FROM | [email protected] | From address |
S3_BUCKET | — | S3 bucket for data persistence |
S3_REGION | — | S3 region |
S3_ENDPOINT | — | S3 endpoint (for DigitalOcean Spaces, MinIO) |
S3_ACCESS_KEY_ID | — | S3 access key |
S3_SECRET_ACCESS_KEY | — | S3 secret key |
S3_PORTAL_PREFIX | portal | S3 key prefix |
S3_FORCE_PATH_STYLE | false | Use path-style S3 URLs |
MAGIC_LINK_TTL_MINUTES | 15 | Magic link expiry |
PORTAL_DISABLED | false | Show “Coming Soon” page, disable purchasing |
DigitalOcean App Platform
1. Push Docker image
./scripts/build-portal.sh
./scripts/build-portal.sh --push
2. Create App
DigitalOcean Dashboard → Apps → Create App:
- Source: Container Registry →
rockfishnetworks/rockfish-portal:latest - HTTP Port:
8080 - Instance size: Basic ($5/mo)
3. Set environment variables
Add all required variables in the App Settings → Environment Variables.
4. Custom domain
App Settings → Domains → Add portal.rockfishndr.com
DNS: Add CNAME record pointing to your-app.ondigitalocean.app
5. Stripe webhook
Stripe Dashboard → Developers → Webhooks → Add:
- URL:
https://portal.rockfishndr.com/webhook/stripe - Events:
checkout.session.completed
Docker (self-hosted)
docker run -d \
--name rockfish-portal \
--env-file .env \
-e BIND_ADDR=0.0.0.0:8080 \
-p 8080:8080 \
rockfish-portal
Startup Connectivity Checks
On startup, the portal tests:
- License server —
GET {LICENSE_SERVER_HOST}/api/v1/health - S3 — attempts to list objects under the configured prefix
Results are logged to help diagnose configuration issues.
S3 Data Layout
s3://{bucket}/{prefix}/
├── users.parquet
├── licenses.parquet
├── pending_licenses.parquet
├── magic_links.parquet
└── reminders.parquet
Retry Logic
If the license server is unavailable when a payment is confirmed:
- License request is queued in
pending_licensestable - Background task retries every 60 seconds
- Up to 100 retries (~100 minutes)
- On success, license is moved to
licensestable and synced to S3