Dashboard and Notifications
Reach InnerWarden's local dashboard and set up notification channels (Telegram, Slack, Discord, webhook, web push) with plain-language explained alerts.
Dashboard and Notifications
For: operators who want to see what InnerWarden is doing and get told when something matters.
InnerWarden gives you two windows onto itself: a local dashboard for sitting down and investigating, and notifications (Telegram, Slack, Discord, webhook, web push) for getting pinged on your phone when there is something to look at. A default install pings you rarely, on purpose, and what pages you by default says exactly when. Both speak plain language, every alert explains what happened and why it matters, not just a raw detector name.
Where to configure each channel (the actual keys and tokens) is owned by Configuration; the commands to set them up live in CLI Reference. This page is what each surface is and how to reach it.
The dashboard
There are two dashboards. The free Community CLI serves its own local one. The paid host stack (Pro and Enterprise) serves the one this section describes; its header reads Enterprise on both, which is a known limitation: the header does not show the licensed tier yet.
Which release this describes. The current release, which is what
/installinstalls, and every screen below ships in it (innerwarden-ctl --versionsays which release a host runs). Most of them arrived in 0.16.67, and each of those sections says so. A host still on 0.16.66 or older has an Overview, a Posture screen with each control and the state the host reports for it, and Cases, a list of cases with their findings, plus other tabs (such as Agents and Evaluation) when the host offers them. It has no lanes, no three-part case, no verdict, block or exclusion on a case, and no Footprint card.
The paid dashboard is a local, authenticated web UI served by the agent. By default it binds to loopback only (127.0.0.1:8787), which is the secure default: nothing on your network can reach it, only something already on the box.
One command tells you everything about how to reach it. It is the paid stack's own command, innerwarden-ctl; the free CLI's innerwarden dashboard opens the free Community dashboard on another port instead:
sudo innerwarden-ctl dashboard
That prints the bind address, the URL, the login, and a ready-to-paste SSH-tunnel command. You do not edit systemd units to manage access anymore, it is one command:
sudo innerwarden-ctl dashboard tunnel # print the exact SSH local-forward command (nothing exposed, safest)
sudo innerwarden-ctl dashboard login # set a login without exposing anything (a tunnel needs one too)
sudo innerwarden-ctl dashboard open # expose it securely: generates a login if none exists,
# sets the bind, and firewall-locks to YOUR current SSH client IP
sudo innerwarden-ctl dashboard open --allow <ip> # lock to a specific IP instead (--public widens to any IP, less safe)
sudo innerwarden-ctl dashboard close # back to localhost only
The safest way to view it remotely is the SSH tunnel: nothing is ever exposed, you forward the port over your existing SSH session and open https://127.0.0.1:8787 on your own machine (the dashboard serves HTTPS with a self-signed certificate unless you give it one, so the browser asks you to accept it once). open is there when you genuinely want it reachable, and it refuses to expose the dashboard without credentials, so an exposed dashboard is always password-protected. The flags for all of these are detailed in CLI Reference.
What answers without a login
The dashboard asks for its login on every connection, including one from the box itself. With no login set, it shows nothing and names the command that sets one. Two groups of routes on the same port are different, and this is all of them.
The agent-facing API answers any caller on the host without a login, because a local agent and the CLI call it; from anywhere else it asks for the login. Its routes, and what each can change:
| Route | What it does |
|---|---|
GET /api/agent/check-ip, POST /api/agent/check-command, POST /api/advisor/check-command | Ask for a verdict on an address or a command (the check endpoints in Connect Your Agent). A command check is recorded with its verdict and can raise an alert. |
GET /api/agent/security-context | Reads the security context an agent is given. |
POST /api/agent/record-attempt | Records one message to a guarded agent and what became of it, only for a caller the process table proves is that agent. |
POST /api/agent-guard/connect, POST /api/agent-guard/disconnect | Register an AI agent with Agent Guard, or remove one from its registry, and save the registry. Operator only (below). |
GET /api/agent-guard/agents | Lists the registered agents. |
GET /metrics | Prometheus metrics. Operator only (below). |
Any process on the host can call the other routes without a login, a process running as a guarded agent included. Three are the operator's (from 0.16.67): /metrics and Agent Guard connect and disconnect skip the login only when the kernel says the connection was opened by root, the InnerWarden service account or a member of the innerwarden group, and not from inside a guarded AI agent. Any other local caller gets a 403 that says what is missing, and the dashboard login still works for it: a local Prometheus scraping as its own user needs that login. An AI agent that runs as root or as a member of the innerwarden group counts as the operator here, so keep the agent's user out of that group. On 0.16.66 and older any process on the host could call all of them, and so add an agent to the Agent Guard registry or remove one from it. None of these routes arms, disarms or rescopes the kernel controls.
The live-feed routes (/api/live-feed and its stream, geoip, honeypot and mitre views) answer anyone who can reach the port, with no login. They serve a sanitised feed, attacker addresses, techniques and reputation, with the host, the evidence and internal incidents left out. They are what a public page such as our /live reads.
Overview: three lanes (from 0.16.67)
The Overview answers three questions, one card each:
- Messages to your AI agent: someone trying to talk your agent into something, and who stopped it.
- What your AI agent did: every command your agent tried, and what InnerWarden decided.
- Attacks on this server: attacks from the internet, the honeypot, and what the kernel caught.
Each card says what its number counts and over which span, how many cases are waiting on you, and links to its latest case and to the full lane in Cases.
Cases and the case detail (from 0.16.67)
Cases lists the same three lanes, plus Everything: every case on the host, including raw telemetry and the bookkeeping of responses, which belong to none of the three lanes. Open a case and it reads in three parts:
- What happened, in plain words.
- What InnerWarden did about it.
- What you need to decide, if anything. A case that needs nothing from you says so.
The technical detail behind each part is one switch away, for when you want to check it rather than read it. How to work a case from there is in Responding to Incidents.
Analyst tools (from 0.16.67)
From a case you can:
- Record a verdict: say what the case was, dismiss it, or keep watching it. Your answer stays on the record.
- Block this address: block the address a case names, after a confirmation step.
- Make an exclusion: tell the host not to flag this again, with a scope (what it has to match) and an expiry you pick, from 1 to 90 days. It ends on its own; there is no forever. An exclusion needs an enrolled second factor: a host with none enrolled makes no exclusion at all, and says how to enrol. With the shipped defaults an exclusion then asks for your code, and every one is announced on your chat channels, naming who made it. Exclusions are listed, and can be revoked, on their own screen, reached from Cases.
Where your second factor covers an action, the dashboard asks for the 6-digit code from your authenticator app before it records anything, and a refused or missing code changes nothing. Which dashboard and chat actions ask for a code by default, and how to change that, is in Trust and Safety Invariants.
Protection (from 0.16.67; Posture on 0.16.66)
Protection lists each control by name, the Execution Gate, the Secret Read Guard and DNS Guard, with the state the host reports for it. How the Execution Gate is armed safely is in Safe Observe and Allowlist; what DNS Guard does, and why it stays in observe until you arm it, is in Editions and Runtime Guarantees.
The Footprint card on the same screen (from 0.16.67) says what InnerWarden stores on the server, how long it keeps each kind of data, and why.
Notifications
Notifications are how InnerWarden reaches you when you are not looking at the dashboard. Every chat channel (Telegram, Slack, Discord) applies the same gate, so you are not spammed. For each incident InnerWarden decides one of three things:
- Send now, to every enabled channel: an active intrusion that is not contained, which includes a Critical reverse shell that is not contained. A Critical attack chain built on reverse-shell evidence is sent whatever the AI or responder state, and a reverse shell to a cloud address is never grouped away as the host's own traffic.
- Fold into the daily briefing: low severity, or already handled.
- Drop: test traffic, an allowlisted address, or something already sent within the cooldown window.
An incident left for you to review is not paged on its own account: it waits in Cases, and it reaches your phone only if it also passes the gate above (a Telegram message for every review item is opt-in, [learning] needs_review_notify = true).
A few things hold across all channels:
- Every alert explains itself. A plain-language "what happened and why it matters" line, with the MITRE technique, not a snake_case detector name.
- Every alert names the server. On a multi-box fleet you can always tell which machine an alert came from.
- Bursts collapse. When 50+ threats are auto-blocked in an hour, you get one "heavy attack, all contained" summary that breaks down the attack types, not 50 pings.
What pages you by default
On a default install a chat message is rare, on purpose. Telegram, Slack and Discord page you for one thing: an intrusion in progress that nothing has contained. In practice that is a Critical incident, such as a kill chain, a shell wired to a remote socket, privilege escalation with persistence or lateral movement, data leaving the host or a rootkit. Once it is contained it stops paging.
These do not page you. That is the policy, not a broken channel:
- Brute force and port scans, whether they were blocked or not. A host on the internet sees them all day.
- Anything already contained, and any incident that is not Critical.
Monitor-only (dry-run) does not change this. The host records what it would have blocked and pages by the same rule, so a quiet phone is not proof of a quiet host. None of it is lost. It is on the dashboard, in innerwarden get incidents, and in the daily briefing. min_severity on Telegram or Slack can only narrow this, it does not widen it (see Configuration). To be told about more, point a webhook at your own tooling (it posts every incident at or above its level), or turn on a Telegram message for each item waiting for your review ([learning] needs_review_notify = true). innerwarden config test-alert proves delivery works, it is not a sample of what will page.
Telegram (the full control surface)
Telegram is the richest channel: it is not just alerts, it is a remote control for InnerWarden from your phone. You get a push alert for each active intrusion (an attack that is still going and not contained), and you can act on it with inline buttons right from the alert: Block the address, Ignore (silences that message and records nothing), and Not a threat (Report FP on the technical profile), which is recorded as a verdict. In guard mode the alert carries Not a threat only, plus a link to investigate when the incident names an address and a dashboard address is set. Beyond that:
/mode guard | watch | dryrunflips the guardian mode live, from your phone, no SSH needed. This is the lever for a lay operator who cannot get to a shell: switch the whole host between observe (dry-run), watch, and active enforcement./unblock <ip>reverses a containment from the same surface that blocked it./status,/incidents,/decisions,/posture,/ask <question>query the box conversationally; free text routes to the AI for a plain answer./statusand/startcount the day's incidents from the store, so an agent restart does not reset them, and count the addresses a rule was actually put on today, so a host that only watches says none were blocked./menuopens an inline keyboard.- An alert says what was done about that incident, not what the mode promises. Only a block that ran on the host reads "Blocked <ip> on this server". A block decided in dry run, or with the responder off, says the block was decided and nothing was blocked. Anything else (no decision yet, a question for you, monitor, a refused or failed block, a later unblock) reads "Needs your attention" ("Operator action required" on the technical profile).
- Settings buttons (profile, sensitivity) actuate at runtime and persist, they do real things, not print a CLI hint.
- The menu is profile-aware: a non-technical operator sees a tiny 5-command menu in plain language (
status · threats · mode · ask · help); the technical profile gets the full set.
Permanent changes (a standing rule, a mode switch, an unblock, an allowlist edit, disabling a detector) can require a TOTP second factor, configured with innerwarden config 2fa; one-shot approvals stay one tap, and every action's level is configurable. See Trust and Safety Invariants for the table. The inbound side fails closed: a misconfigured chat id drops all updates rather than authorising a stranger. If Telegram refuses the bot token itself, the agent stops polling after that one refusal and logs a single warning that names the fix, sudo innerwarden-ctl config telegram; until the agent is restarted with a good token, Telegram alerts and buttons do not work. Other poll errors back off, from 5 seconds up to a minute between attempts, or longer when Telegram asks for it.
Slack and Discord
Slack (Incoming Webhook, Block Kit) and Discord (Incoming Webhook, colour-coded embeds) get full parity with Telegram for what matters: incident alerts, post-action reports ("Threat neutralized, Blocked ...") for a block that ran on an immediate threat, a line when an operator unblocks an address (or the unblock fails), burst summaries, and the once-a-day Security Briefing. They render the same explained-alert story and name the same server. One channel failing never blocks the others.
Webhook and web push
- Webhook posts every incident at or above its own minimum severity as HTTP JSON to any endpoint you give it, as it happens. It does not pass through the chat gate: the gate decides what pages a person, and a SIEM or automation gets the whole record.
- Web push delivers browser notifications (RFC 8291 / VAPID), with a configurable minimum severity. Useful when you live in a browser, not a chat app.
The daily Security Briefing
Once a day, InnerWarden sends a boss-readable briefing to your chat channels (Telegram, Slack, Discord): a plain one-line verdict (whether anything got in, how many calls are yours to make, or else whether the night was quiet or busy), what it blocked and the top attacking sources, how many automatic decisions it made, and how many items still need you, counted the same way as the dashboard Overview's "N addresses are waiting on you" line at the moment the briefing is sent. No raw detector names, no "more decisions than events?" confusion. It leads with the server name so a shared channel stays legible across boxes.
The briefing counts the day's incidents from the store the dashboard reads, by the host's local day, so an agent restart during the day never turns a day with a Critical into "nothing critical". The same holds for the daily summary, innerwarden-ctl report and innerwarden-ctl status.
The briefing only claims what was done. A block counts only when a rule was put on the host. On a host that is watching and not blocking (the responder is off, or it is in dry-run), the briefing says it only watched, and lists how many addresses it would have blocked, instead of saying they were blocked or that nobody got in.
Explained Alerts
This is worth calling out on its own because it is the difference between an alert you trust and noise you ignore. Every dispatched notification, and every incident in the terminal and dashboard, carries a plain-language explanation fused from a curated detector catalog and the live MITRE mapping. So kernel_devnode_exposed reaches you as a sentence a human can act on, with the technique it corresponds to. It is communication-only: it changes nothing about detection, severity, or whether the alert was sent, it only makes what InnerWarden already decided legible to you.
Setting it all up
Configure any channel with the matching command (each writes the TOML for you):
innerwarden config telegram # asks for the bot token and chat id
innerwarden config slack --webhook-url <URL>
innerwarden config discord
innerwarden config webhook --url <URL>
innerwarden config dashboard --user <U> # asks for the password
innerwarden config 2fa
The exact flags are owned by CLI Reference, and the full set of config keys (including min_severity, the daily-summary hour, and the notification gate's thresholds) is owned by Configuration. To fire a one-off test to every configured channel, use the test-alert command shown in CLI Reference.
See also
- Responding to Incidents: handling a live alert from these surfaces
- Configuration: every config key for dashboard and channels
- CLI Reference: every command and flag
- Everyday Operations: the daily-check workflow
- Safe Observe and Allowlist: the right way to trust a benign source
- ISO 27001 Mapping: the controls the Compliance endpoint maps