-
-
Notifications
You must be signed in to change notification settings - Fork 30
Configuration
For: operators - the canonical TOML and environment-variable reference, organised by area.
InnerWarden has two config files: one for the sensor (/etc/innerwarden/config.toml, what to collect and detect) and one for the agent (/etc/innerwarden/agent.toml, how to triage, notify, and respond). Both have sane defaults, so a fresh install runs fine with almost nothing set. You only edit these when you want to change behaviour.
You almost never need to hand-edit. The innerwarden config <...>, innerwarden trust, and innerwarden setup commands write these keys for you and re-check them. This page is the reference for what each key means; the commands that set them live on CLI Reference. After any hand-edit, run innerwarden config validate (it fails loudly on a typo'd or unknown key, before the agent boots).
A note that saves confusion: the responder ships off and dry-run on a fresh install. Arming enforcement is a deliberate step. Read Safe Observe and Allowlist before you set responder.dry_run = false.
Most installs need only a handful of keys. Everything else has a working default. A typical small server:
# agent.toml
[ai]
enabled = true
provider = "openai" # or "ollama" for local, or set [ai.warden] for on-device
model = "gpt-4o-mini"
[telegram]
enabled = true # operator alerts on your phone
[responder]
enabled = false # start OFF; arm later, see Safe Observe and Allowlist
dry_run = true # SAFETY: always starts true
block_backend = "ufw"# config.toml (sensor)
[agent]
host_id = "my-server" # the name that appears on every alert
[output]
data_dir = "/var/lib/innerwarden"That is enough. The rest of this page is the full menu, by area.
The sensor decides what to collect and which detectors run. It is deterministic: no AI, no HTTP. Most detectors are on sensible defaults; the advanced ones default to off so you can enable them after the host has a baseline.
[agent]
host_id = "my-server" # appears on every alert and report
[output]
data_dir = "/var/lib/innerwarden"
write_events = true
# redis_url = "redis://127.0.0.1:6379" # optional: stream events/incidents to Redis Streams
# redis_stream = "innerwarden:events"
# redis_maxlen = 50000 # approximate cap via MAXLEN ~Each collector reads one source. The defaults match a typical Debian/Ubuntu host.
[collectors.auth_log]
enabled = true
path = "/var/log/auth.log"
[collectors.journald]
enabled = true
units = ["sshd", "sudo", "kernel"] # "sshd" not "ssh"; "kernel" enables firewall/port-scan signals
[collectors.docker]
enabled = true
[collectors.integrity]
enabled = true
poll_seconds = 60
paths = ["/etc/ssh/sshd_config", "/etc/sudoers"]
[collectors.exec_audit]
enabled = false
path = "/var/log/audit/audit.log"
include_tty = false # high privacy impact; enable only with explicit authorisation
[collectors.macos_log]
enabled = false # macOS only; uses `log stream`, replaces auth_log + journald on Darwin
[collectors.syslog_firewall]
enabled = false # alternative to journald.kernel for hosts without journald
path = "/var/log/syslog" # feeds the port_scan detectorThe native network capture (DNS over UDP:53, HTTP over TCP:80, and TLS JA3/JA4) plus the eBPF programs need no config beyond the kernel capabilities the installer grants. There are no external log-reader collectors (no Falco/Suricata/Wazuh/osquery ingest); the native eBPF + raw-packet coverage is the surface. If you must ingest another tool's logs, see Integration Recipes.
A few detectors are on by default; the rest default to enabled = false so you can switch them on once the host's normal traffic is known. Each typically takes a threshold and a window. What each one catches is on What It Detects.
# On by default
[detectors.ssh_bruteforce]
enabled = true
threshold = 8
window_seconds = 300
[detectors.setns_owner]
enabled = true
cooldown_seconds = 300 # root joining a non-root-owned user namespace
[detectors.untrusted_root_exec]
enabled = true
cooldown_seconds = 300 # uid-0 exec from an unprivileged-writable path
# Enable after a baseline (representative subset; see What It Detects for the full list)
[detectors.credential_stuffing]
enabled = false
threshold = 6 # distinct usernames per IP in window
window_seconds = 300
[detectors.port_scan]
enabled = false
threshold = 12 # unique destination ports per IP in window
window_seconds = 60
[detectors.reverse_shell]
enabled = false
[detectors.ransomware]
enabled = false
[detectors.dns_tunneling]
enabled = false
[detectors.crypto_miner]
enabled = falseA set of detectors is always on and needs no config: docker_anomaly, integrity_alert, c2_callback, container_escape, distributed_ssh, suspicious_login, process_tree, and privesc, among others.
Prefer
innerwarden system tuneover editing thresholds by hand: it proposes adjustments from this host's recent noise and signal. To silence a single noisy-but-benign detector, useinnerwarden trust suppress <pattern>rather than disabling it. Both are on CLI Reference.
The agent is the interpretive layer: triage, correlation, response, and notifications. Every field has a default, so the file is optional.
Set by innerwarden config ai.
[ai]
enabled = true
provider = "openai" # openai | anthropic | ollama | groq | deepseek | together |
# minimax | mistral | xai | gemini | fireworks | openrouter | azure_openai
# api_key = "" # or env OPENAI_API_KEY / ANTHROPIC_API_KEY
model = "gpt-4o-mini"
# base_url = "" # endpoint override (ollama + OpenAI-compatible providers); or env OLLAMA_BASE_URL
context_events = 20 # recent events sent to the AI as context
confidence_threshold = 0.85 # below this, no auto-execution
incident_poll_secs = 2
max_ai_calls_per_tick = 5 # 0 = unlimited
circuit_breaker_threshold = 0 # 0 = disabled; ~20 recommended for DDoS defense
circuit_breaker_cooldown_secs = 60For on-device triage with no API key, install the Local Warden model (innerwarden install-warden) and it writes its own [ai.warden] section. AI is optional: the deterministic detectors plus the on-device model already give a working system.
The single most important block. Set by innerwarden config responder.
[responder]
enabled = false # default; a fresh install does not enforce
dry_run = true # SAFETY: always starts true (log what it would do, do nothing)
block_backend = "ufw" # ufw | iptables | nftables | firewalld | pf | xdp
# firewalld = native RHEL/Rocky/CentOS/Fedora/openSUSE backend
allowed_skills = ["block-ip-ufw", "monitor-ip"]dry_run = false is live enforcement. Do not set it before a clean baseline and a verified trust list, see Safe Observe and Allowlist and Trust and Safety Invariants.
Set by innerwarden trust add / trust remove. Trusted entities are still detected and notified; only their auto-block is suppressed.
[allowlist]
trusted_ips = [] # IPs or CIDRs that skip the AI gate, e.g. ["10.0.0.0/8", "192.168.1.100"]
trusted_users = [] # usernames that skip the AI gateNever populate this blindly. The never-blind observe, verify, propose, arm workflow is owned by Safe Observe and Allowlist.
These tune how the agent stitches events into chains and how it handles ambiguous alerts. Sensible defaults; an absent section uses them.
[correlation]
enabled = true
window_seconds = 300
max_related_incidents = 8
[observation]
enabled = true
auto_dismiss_threshold = 70 # min score to auto-dismiss without AI
auto_escalate_threshold = 40 # max score to auto-escalate without AI
ai_verification = true # send the ambiguous middle band to AI
ai_batch_size = 10
maintenance_windows = [] # ["HH:MM-HH:MM", ...] local time; items inside get a context bump
[learning]
suppression_mode = "shadow" # off | shadow | enforce (shadow logs but changes nothing)
min_dismissals = 5 # repeated operator dismissals before a pattern is eligible to auto-suppress
llm_escalation_enabled = true
llm_escalation_min_confidence = 0.75 # below this, a high-impact escalation waits for a human
needs_review_notify = false # Telegram message with Block/Ignore/Dismiss buttons for parked items
emit_labels = trueAmbiguous alerts that score in the middle band wait for your decision rather than auto-acting. Low and medium parked items auto-resolve to "dismiss" after 24 hours; high and critical never auto-close and stay visible until a human acts. How you act on them is on Responding to Incidents.
All set by the innerwarden config <channel> commands on CLI Reference; what they look like is on Dashboard and Notifications.
[telegram]
enabled = false
# bot_token = "" # or env TELEGRAM_BOT_TOKEN
# chat_id = "" # or env TELEGRAM_CHAT_ID
# daily_summary_hour = 8 # optional daily summary at this local hour (0-23)
[slack]
enabled = false
# webhook_url = "" # or env SLACK_WEBHOOK_URL
min_severity = "high"
# dashboard_url = "" # optional deep-link in messages
[discord]
enabled = false
# webhook_url = "" # or env DISCORD_WEBHOOK_URL
min_severity = "high"
[webhook]
enabled = false
url = "https://hooks.example.com/notify"
min_severity = "medium" # debug | info | low | medium | high | critical
timeout_secs = 10
[web_push]
enabled = false
min_severity = "high"
# VAPID keys are generated by: innerwarden config web-pushBind and access are best managed with innerwarden dashboard (it writes [dashboard] bind and restarts the agent safely); see CLI Reference and Dashboard and Notifications. The remaining knobs:
[dashboard]
# trusted_proxies = ["127.0.0.1", "::1"]
session_timeout_minutes = 480 # 8h inactivity timeout for Bearer sessions
max_sessions = 5 # max concurrent sessions; oldest evicted when exceeded[abuseipdb]
enabled = false
# api_key = "" # or env ABUSEIPDB_API_KEY
auto_block_threshold = 0 # 0 = disabled; 80+ recommended for known-botnet blocking
[dshield]
enabled = false # SANS Internet Storm Center, keyless, read-only IP reputation
[geoip]
enabled = false # ip-api.com, no key
[fail2ban]
enabled = false # polls fail2ban-client CLI; Linux only
[cloudflare]
enabled = false
# zone_id = ""
# api_token = "" # or env CLOUDFLARE_API_TOKEN
auto_push_blocks = true
block_notes_prefix = "InnerWarden:"
[crowdsec]
enabled = false # EXPERIMENTAL: large community lists need ipset/nftables backend
# lapi_url = "http://localhost:8080"
# api_key = "" # or env CROWDSEC_API_KEY
poll_secs = 60
max_per_sync = 50 # cap new IPs blocked per tick (avoids OOM on large lists)Set by innerwarden config mesh / innerwarden mesh connect.
[mesh]
enabled = false
bind = "0.0.0.0:8790"
poll_secs = 30
auto_broadcast = true
max_signals_per_hour = 50
initial_trust = 0.7 # trust level for configured peers (0.0-1.0)
# [[mesh.peers]]
# endpoint = "https://peer1:8790"
# public_key = "" # may be empty; discovered via /mesh/ping at startup
# label = "dc-east"A behavioural trap. Off-by-cost by default (banner mode just responds to probes). The interaction modes:
interaction |
What it does |
|---|---|
banner (default) |
Sends a fake banner, captures connection metadata, never authenticates. Lowest cost, least signal. |
medium |
Captures every credential attempt and rejects all of them. No shell ever opens. |
llm_shell |
Tiered SSH auth, then an LLM-backed fake shell that records every command. Catches Mirai-class bots and human attackers. Opt-in. |
[honeypot]
mode = "demo" # demo | listener | always_on
bind_addr = "127.0.0.1" # "0.0.0.0" + allow_public_listener for a production trap
port = 2222
http_port = 8080
services = ["ssh"] # ["ssh", "http"] for multi-service
interaction = "banner" # banner | medium | llm_shell
allow_public_listener = false
max_connections = 64
forensics_keep_days = 7The full honeypot block (sandbox, containment, external handoff, redirect) has many more keys; run innerwarden config validate after editing and see the in-file comments. The weak-credential list used by llm_shell mode is compiled in by design.
Operator-authored response runbooks. Disabled by default so a fresh install never auto-runs block/suspend steps. Built-ins are always embedded; list them with innerwarden rule list --type playbooks.
[playbooks]
enabled = false # master switch
rules_dir = "/etc/innerwarden/rules/playbooks"
shadow = false # when enabled, run for the audit trail only (no skill fires)[agent]
tags = [] # e.g. ["env=prod", "role=web"]; scope playbooks to a host roleRetention is owned by Privacy and GDPR (it documents both the schedule and the GDPR export/erase contract). The keys live in agent.toml:
[data]
events_keep_days = 7
incidents_keep_days = 30
decisions_keep_days = 90
telemetry_keep_days = 14
reports_keep_days = 30Secrets and overrides. Put them in /etc/innerwarden/agent.env (or .env locally); it loads at startup and is fail-silent if absent. Anything set in TOML can also come from the matching env var.
# AI providers
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
OLLAMA_BASE_URL=http://localhost:11434 # optional override
# Notifications
TELEGRAM_BOT_TOKEN=<id>:<secret>
TELEGRAM_CHAT_ID=<numeric>
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
# Enrichment
ABUSEIPDB_API_KEY=...
CLOUDFLARE_API_TOKEN=...
VT_API_KEY=... # or VIRUSTOTAL_API_KEY; threat-feed hash checks
# DShield and GeoIP are keyless: enable via [dshield] / [geoip], no env var needed
# Dashboard auth
INNERWARDEN_DASHBOARD_USER=admin
INNERWARDEN_DASHBOARD_PASSWORD_HASH=$argon2id$... # generated by innerwarden config dashboard
# Debug
RUST_LOG=innerwarden_agent=debug- The commands that write all of these keys: CLI Reference.
- Arming enforcement safely, the never-blind allowlist: Safe Observe and Allowlist.
- Systemd services, the supervisor, file permissions, and where data lives: Everyday Operations.
- What each detector catches: What It Detects.
- Retention and data-subject requests: Privacy and GDPR.