Skip to content

Configuration

Maicon Ribeiro Esteves edited this page Jun 20, 2026 · 13 revisions

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.


Minimum useful config

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.


Sensor (config.toml)

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.

Core

[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 ~

Collectors

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 detector

The 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.

Detectors

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 = false

A 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 tune over editing thresholds by hand: it proposes adjustments from this host's recent noise and signal. To silence a single noisy-but-benign detector, use innerwarden trust suppress <pattern> rather than disabling it. Both are on CLI Reference.


Agent (agent.toml)

The agent is the interpretive layer: triage, correlation, response, and notifications. Every field has a default, so the file is optional.

AI provider (config ai)

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 = 60

For 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.

Responder and safety (config responder)

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.

Allowlist and trust (trust add)

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 gate

Never populate this blindly. The never-blind observe, verify, propose, arm workflow is owned by Safe Observe and Allowlist.

Correlation, observation, and learning

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 = true

Ambiguous 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.

Notifications

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-push

Dashboard

Bind 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

Enrichment integrations

[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)

Mesh (collaborative defense)

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"

Honeypot

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 = 7

The 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.

SOC playbooks

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)

Host tags

[agent]
tags = []                    # e.g. ["env=prod", "role=web"]; scope playbooks to a host role

Data retention

Retention 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 = 30

Environment variables

Secrets 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

Where to go next

Clone this wiki locally