Day-to-day operation of Heimdall. Covers the deployment lifecycle from learning through enforcement, CLI commands, TUI dashboard, process tagging, SIEM integration, and troubleshooting.
- Deployment Lifecycle
- CLI Reference
- TUI Dashboard
- Process Tagging
- Reading Verdicts
- SIEM Integration
- Offline / Air-Gapped Operation
- Troubleshooting
Heimdall is designed to be deployed in phases. Do not skip steps.
The daemon observes all filesystem writes and builds per-process, per-directory baselines. No alerts are generated. No processes are affected.
# Confirm you're in learning mode
heimdall mode
# → Current mode: LEARNING
# Watch events flow in (optional, just to verify it's working)
heimdall watchWhat's happening: Every write event builds an entry in the SQLite baseline database. The EMA (Exponential Moving Average) needs at least min_samples (default: 100) observations per process/directory pair before the baseline is considered valid. For most hospital servers, 30 days captures daily/weekly patterns in backup jobs, log rotation, database maintenance, etc.
When to move on: When baseline counts are in the hundreds or thousands for your key processes. Check with:
heimdall status --verbose
# Look at "Baselines tracked" — should be non-trivialSwitch to monitoring mode. The daemon now compares live writes against baselines and generates alerts, but does not stop any processes.
sudo heimdall mode monitoringWhat to do in this phase:
-
Watch the alert stream:
heimdall watch # or heimdall-tui -
Review recent alerts:
heimdall logs --limit 50
-
Identify false positives. Common sources:
- Backup software (writes compressed/encrypted data — high entropy is normal for it)
- Database engines (WAL writes can spike entropy during checkpoints)
- Package updates (new binaries have different entropy profiles)
-
Tag processes to set their policy:
# Your backup software is not ransomware heimdall tag set bacula-fd trusted heimdall tag set /backup/ trusted # Your EHR database must never be interrupted heimdall tag set mysqld critical heimdall tag set postgres critical
-
Tune thresholds if needed (edit
/etc/heimdall/heimdall.toml):- Too many false positives → raise
entropy_threshold(try 4.0 or 5.0) - Missing obvious anomalies → lower
entropy_threshold(try 2.0)
- Too many false positives → raise
When to move on: When the alert stream is clean — you understand every alert you're seeing and false positives are tagged appropriately.
Switch to enforcing mode. The daemon now executes policy actions: freeze (SIGSTOP) or kill (SIGKILL) on processes that match the severity/tag policy matrix.
sudo heimdall mode enforcingBefore enabling enforcement, ensure you have:
- Tagged every critical service as
critical(these are never stopped) - Tagged trusted infrastructure as
trusted(alert only) - Reviewed the policy matrix (see Process Tagging)
- Tested with
watchedtags on non-critical services first - A way to quickly disable enforcement if something goes wrong:
sudo heimdall mode monitoring
All commands communicate with the running daemon via unix socket. The daemon must be running.
Show daemon status: mode, uptime, event count, baseline count, active alerts.
heimdall status # Plain output
heimdall status --verbose # Adds events/sec, raw uptimeShow recent detection verdicts.
heimdall logs # Last 20 verdicts
heimdall logs --limit 100 # Last 100
heimdall logs --verbose # Raw numerical data
heimdall logs --limit 50 --verboseLive-stream events from the daemon. Stays connected until Ctrl+C.
heimdall watch # Plain English
heimdall watch --verbose # Raw dataGet or set the operating mode.
heimdall mode # Show current mode
sudo heimdall mode learning # Switch to learning
sudo heimdall mode monitoring # Switch to monitoring
sudo heimdall mode enforcing # Switch to enforcingMode changes take effect immediately. No daemon restart required.
Manage process and path tags.
# Set a tag
heimdall tag set <pattern> <tag>
# List all tags
heimdall tag listValid tags: critical, trusted, watched, untrusted
# Examples
heimdall tag set mysqld critical
heimdall tag set /opt/ehr/ critical
heimdall tag set /tmp/ watched
heimdall tag set suspicious-binary untrusted
heimdall tag list# Use a non-default socket path
heimdall --socket /path/to/heimdall.sock statusheimdall-tui
# Custom socket
heimdall-tui --socket /path/to/heimdall.sockThe dashboard shows three sections:
- Top bar: Connection status (green/red), operating mode, uptime, events/sec, total events, alert count
- Event feed (left): Scrollable list of recent verdicts, color-coded by severity
- Process leaderboard (right): Top 20 processes ranked by highest anomaly score
| Key | Action |
|---|---|
q |
Quit |
v |
Toggle verbose/plain English mode |
m |
Cycle mode (learning → monitoring → enforcing → learning) |
/ |
Enter filter mode — type to filter events by process name, path, or summary |
Esc |
Cancel filter input |
Enter |
Apply filter |
Up/Down |
Scroll event feed |
Home |
Jump to oldest event |
End |
Jump to newest event |
The TUI auto-reconnects if the daemon restarts. You'll see "DISCONNECTED" in red until the connection is restored.
Tags determine what happens when Heimdall detects an anomaly from a specific process or path. In enforcing mode, the policy matrix is:
| Tag | Low | Medium | High | Critical |
|---|---|---|---|---|
| critical | alert | alert | alert | alert |
| trusted | alert | alert | alert | alert |
| watched | alert | alert | freeze | freeze |
| untrusted | alert | alert | freeze | kill |
- Alert: Log the event, send to sinks. Process is not affected.
- Freeze: Send SIGSTOP to the process. It pauses until manually resumed with
kill -CONT <pid>. - Kill: Send SIGKILL to the process. It terminates immediately.
Before sending any signal, Heimdall verifies the PID still belongs to the expected process by checking /proc/<pid>/comm. If the PID has been reused by a different process, the signal is not sent.
# === Life-critical / must never be interrupted ===
heimdall tag set mysqld critical
heimdall tag set postgres critical
heimdall tag set java critical # EHR application server
heimdall tag set /opt/ehr/ critical # All writes to EHR directories
heimdall tag set /data/pacs/ critical # PACS imaging archive
heimdall tag set /data/dicom/ critical # DICOM storage
# === Known infrastructure ===
heimdall tag set sshd trusted
heimdall tag set systemd-journald trusted
heimdall tag set rsyslogd trusted
heimdall tag set crond trusted
heimdall tag set bacula-fd trusted # Backup agent
heimdall tag set /backup/ trusted # Backup target directory
# === Recently deployed or updated software ===
heimdall tag set /opt/newapp/ watched
# Everything else defaults to "untrusted"Pattern rules:
- Patterns without
/match process names exactly (e.g.,mysqld) - Patterns ending with
/match directory prefixes (e.g.,/opt/ehr/matches/opt/ehr/data/file.db) - Patterns without trailing
/match exact paths only - Minimum pattern length is 2 characters
Designed for operators who don't need to understand entropy math:
14:23:01 [LOW] mysqld writing to /var/lib/mysql/ibdata1 — normal behavior.
14:23:05 [MEDIUM] java write pattern to /opt/ehr/logs/audit.log is slightly unusual. Confidence: moderate.
14:23:12 [CRITICAL] unknown_proc is writing scrambled data to /data/patient_records/db.mdf, this is highly unusual. Confidence: very high.
Action: kill
The summary tells you: who is writing, where, and how concerned you should be.
For engineers who want the raw data:
14:23:12 [CRITICAL] pid=6291 comm=unknown_proc path=/data/patient_records/db.mdf
entropy=7.9312 deviation=14.2σ freq_ratio=1.0x anomaly=0.9847 size=65536B
entropy=7.93 baseline_mean=4.12 baseline_stddev=0.27 deviation=14.2σ write_size=65536 freq_ratio=1.0x samples=4821
Action: kill
Field reference:
| Field | Meaning |
|---|---|
entropy |
Shannon entropy of the write buffer (0.0-8.0). >7.0 is suspicious. |
deviation |
How many standard deviations from the baseline mean. Higher = more anomalous. |
freq_ratio |
Write frequency compared to baseline. 5.0x = writing 5 times more often than normal. |
anomaly |
Combined anomaly score (0.0-1.0). Weighted: 70% entropy, 30% frequency. |
size |
Bytes written in this call. |
baseline_mean |
The process's normal entropy for this directory. |
baseline_stddev |
How much normal entropy varies for this process. |
samples |
How many observations the baseline is built from. More = more reliable. |
Heimdall outputs structured JSON to all configured sinks. Events are dispatched concurrently — a slow or unreachable SIEM never blocks detection.
Every event written to sinks is a serialized EntropyVerdict:
{
"event": {
"pid": 6291,
"tgid": 6291,
"comm": "suspicious",
"path": "/data/records/file.dat",
"write_size": 65536,
"entropy": 7.93,
"timestamp": "2026-02-10T14:23:12.456Z",
"buffer_sample": [...]
},
"entropy_deviation": 14.2,
"frequency_ratio": 1.0,
"anomaly_score": 0.9847,
"severity": "critical",
"summary": "suspicious is writing scrambled data to /data/records/file.dat...",
"detail": "entropy=7.93 baseline_mean=4.12 ...",
"action_taken": "kill"
}[[sinks]]
type = "http"
url = "https://splunk.hospital.local:8088/services/collector/event"
[sinks.headers]
Authorization = "Splunk your-hec-token-here"[[sinks]]
type = "http"
url = "https://elastic.hospital.local:9200/heimdall-events/_doc"
[sinks.headers]
Authorization = "ApiKey your-api-key-here"[[sinks]]
type = "syslog"
address = "udp://10.0.1.5:514"Messages are sent as RFC 5424 structured data with the JSON verdict as the payload. Messages exceeding 65,000 bytes are truncated.
You can configure any combination. All sinks receive every event:
[[sinks]]
type = "file"
path = "/var/log/heimdall/events.json"
[[sinks]]
type = "syslog"
address = "udp://siem.hospital.local:514"
[[sinks]]
type = "http"
url = "https://splunk.hospital.local:8088/services/collector/event"
[sinks.headers]
Authorization = "Splunk your-token"Heimdall is designed for environments with no external network access. This is the default — no configuration required.
What works without any network:
- eBPF probes (kernel-level, no network)
- Detection engine (local computation)
- SQLite baselines (local file)
- Unix socket IPC (local)
- File sink (local disk)
- CLI and TUI (connect via local socket)
What requires network (all optional):
- Syslog sink (UDP to your local SIEM — internal network only)
- HTTP sink (HTTPS to your local SIEM — internal network only)
What requires network at build time only:
cargo buildfetches crates from crates.io — usecargo vendorfor air-gapped builds (see INSTALL.md)
There is no:
- Telemetry
- Cloud connectivity
- Signature database updates
- License server
- Phone-home of any kind
The detection model is statistical (per-machine baselines), not signature-based. It never needs to download anything. Once installed, it runs indefinitely on its own.
The CLI/TUI can't find the daemon socket.
# Check if daemon is running
sudo systemctl status heimdall
# Check socket exists
ls -la /run/heimdall/heimdall.sock
# If the directory doesn't exist
sudo mkdir -p /run/heimdall
sudo systemctl restart heimdallHeimdall refuses to start if anyone can edit the config.
sudo chmod 640 /etc/heimdall/heimdall.toml
sudo chown root:root /etc/heimdall/heimdall.tomlCheck the error message in the journal:
sudo journalctl -u heimdall --no-pager | tail -20Common issues:
alpha must be in (0.0, 1.0)— check engine.alpha is between 0 and 1entropy_threshold must be > 0.0— can't be zero or negativemin_samples must be >= 10— needs at least 10sample_size must be <= 65536— max 64KB
-
Check what's triggering alerts:
heimdall logs --limit 50 --verbose
-
Tag known-good processes:
heimdall tag set backup-process trusted heimdall tag set /backup/ trusted
-
Raise the entropy threshold in
/etc/heimdall/heimdall.toml:entropy_threshold = 4.0 # or 5.0 for conservative
-
Restart the daemon:
sudo systemctl restart heimdall
If heimdall status shows Events processed: 0 after the daemon has been running:
- The eBPF probes may not be attached (check
journalctl -u heimdallfor probe loading errors) - Your kernel may not support the required eBPF features — verify kernel 5.8+ and BTF support
A major software update changes write patterns. Options:
- Temporarily switch to learning mode to absorb the new patterns:
sudo heimdall mode learning # Wait a few hours/days sudo heimdall mode monitoring - The EMA will naturally adapt (alpha=0.05 means ~5% of each new observation is absorbed), but this takes hundreds of observations.
Resume it immediately:
# Find the PID
ps aux | grep <process-name>
# Resume it
kill -CONT <pid>
# Then tag it to prevent future freezes
heimdall tag set <process-name> criticalThe file sink at /var/log/heimdall/events.json grows indefinitely. Set up log rotation:
# /etc/logrotate.d/heimdall
sudo tee /etc/logrotate.d/heimdall << 'EOF'
/var/log/heimdall/events.json {
daily
rotate 14
compress
delaycompress
missingok
notifempty
copytruncate
}
EOF