Security and privacy
What’s encrypted at rest
Section titled “What’s encrypted at rest”Every secret InfraInbox has to read back later — destination credentials (a Telegram bot token, a Discord or Slack webhook URL, a signed webhook’s signing secret), TOTP seeds, push keys — is encrypted with AES-256-GCM before it reaches the database. Each value gets its own randomly generated data key; that data key is itself wrapped under the server’s master key, and the ciphertext is bound to the exact row and column it lives in, so a blob copied into another row, another field or another workspace fails to decrypt.
The master key never lives in the database. By default it’s generated on first start and stored as a single file (master.key) inside the infrainbox-data volume, mode 0600; you can supply your own instead (see Configuration reference). A short HMAC “key-check value” stored in the database lets the server detect a wrong or missing key at start-up, without storing anything that would let someone recover the key itself. See Backups and upgrades for rotating it.
This protects a stolen database dump or backup — someone with only the SQL dump learns nothing about your destination credentials. It doesn’t protect against a compromised, running server: a process that already holds the master key in memory can decrypt what it needs to send notifications.
What leaves the server
Section titled “What leaves the server”InfraInbox doesn’t phone home. There’s no telemetry, no analytics and no update check that reports anything about your instance anywhere. The only outbound traffic it ever makes is:
- to the destinations you configure yourself (Telegram, Slack, Discord, a webhook URL you set, and so on);
- to InfraInbox’s push relay, if you use the mobile app and haven’t disabled it — see Push notifications below for what the relay can and can’t see;
- to an OpenTelemetry tracing endpoint, only if you explicitly configure one.
Push notifications
Section titled “Push notifications”A push notification’s payload is end-to-end encrypted with the Web Push standard (RFC 8291): it’s addressed and encrypted for the paired device’s own key before it ever leaves InfraInbox. Whichever path carries it there — InfraInbox’s own relay (for the App Store build, which can’t hold a direct connection to Apple’s push service itself) or Apple’s push service directly, for a self-built client — only sees encrypted bytes and the minimum routing information the platform requires. It cannot read the incident title, body or any other content.
Outgoing connections and SSRF protection
Section titled “Outgoing connections and SSRF protection”Whenever InfraInbox makes an outbound request on your behalf — a webhook, a chat provider, a push endpoint, a “send test” — it goes through one guarded HTTP client that checks the resolved IP address before every connection, not just the hostname (so DNS rebinding and decimal/octal/hex address tricks don’t bypass it):
| Destination | Self-hosted | Cloud |
|---|---|---|
| Public addresses | Always allowed | Ports 80/443 only |
| Private (RFC 1918) addresses | Always allowed | Never |
Carrier-grade NAT (100.64.0.0/10), IPv6 unique local (fc00::/7) |
Only if you opt in (outbound.allow_cgnat / outbound.allow_ula) |
Never |
Loopback, link-local, multicast, cloud metadata addresses (169.254.169.254 and equivalents) |
Never, under any setting | Never |
This is why a webhook or chat destination pointed at a private IP works out of the box on a self-hosted instance (many homelabs send notifications to something on the same LAN), while cloud metadata endpoints and the loopback interface stay blocked regardless of any setting. Redirects are never followed automatically, and every outbound request has its own timeout and a capped response size.
Sessions
Section titled “Sessions”Signing in to the dashboard sets a single session cookie, __Host-iib_session — the __Host- prefix means the browser only ever sends it back to this exact server (no path, no subdomain can plant or read it), and it’s Secure, HttpOnly and same-site strict. The session token itself is a random 256-bit value; only its SHA-256 hash is stored, so a stolen database row can’t be turned back into a usable cookie. A session lasts up to 30 days from sign-in, or 7 days since it was last used, whichever comes first, and you can revoke individual sessions or sign out everywhere from the dashboard.
Every state-changing request that carries the session cookie is checked against the browser’s own Sec-Fetch-Site/Origin headers before it’s allowed to run, so a forged request from another site — even one on the same parent domain — is rejected before it can act on your account.
Signing in through an identity provider keeps its state server-side: the browser holds only a short-lived flow cookie naming the flow, deleted as soon as the flow ends. An OpenID Connect ID token is accepted only if its signature, issuer, audience, expiry and nonce all check out, and the account is found by the provider’s subject, never by email. The session records which provider it came through, and so does the audit log’s sign-in entry.
Two-factor authentication
Section titled “Two-factor authentication”InfraInbox supports TOTP (RFC 6238, 30-second codes, the same kind of authenticator app used everywhere else). Confirming enrollment issues ten single-use recovery codes; only their hashes are stored, and each is invalidated the moment it’s used. An administrator can require a second factor instance-wide, or just for workspace owners and admins — see auth.require_mfa in the configuration reference.
Inside a workspace, a member has one of three roles — owner, admin or member — each with more write access than the last; every role can read the inbox, sources, incidents and rules. Changing workspace settings, routing rules, destinations or retrying a delivery needs the admin or owner role. Separately, an instance administrator is a server-wide account (created at first-run setup, or with infrainbox admin create) used for recovery and cross-workspace administration, distinct from any workspace’s roles.
Source and heartbeat keys
Section titled “Source and heartbeat keys”An ingest key (iik_src_…) or heartbeat key (iik_hb_…) is a single random value with about 190 bits of entropy. It’s never stored in a form that could be read back — only its SHA-256 hash, in a database column looked up by that hash. There’s deliberately no additional slow hashing (Argon2, bcrypt) on these: at that much entropy it adds no real protection, and hashing every single ingest request with a slow algorithm would turn each one into a CPU/memory cost an attacker could use to overload the server. A leaked key can be rotated from the source’s or monitor’s page without touching anything else.
Tenant isolation
Section titled “Tenant isolation”Every workspace-scoped table carries the workspace on the row, every query filters by it, and a linter fails the build on a query that does not. A cross-tenant reference cannot even be stored: the foreign keys are composite, on (workspace, id). Under all of that, PostgreSQL’s own row-level security is a backstop: the login the API runs as is not the tables’ owner, each request’s transaction declares the workspaces its credential reaches, and a policy on every one of those tables limits the rows it can see or write to that set — so a query that lost its filter returns nothing rather than somebody else’s rows. Background work (sweeps, delivery, the retention purge) crosses workspaces by design, through its own role and policy; no role can bypass row-level security altogether.
Deleting an account or a workspace
Section titled “Deleting an account or a workspace”An owner can close a workspace, and anyone can close their own account, from Settings; both ask you to re-authenticate and to type the name back first. Closing takes effect with the answer: the memberships go, the workspace’s ingest and heartbeat keys stop working, and nothing more is sent for it. The rows themselves are removed by the background purge — within 30 days at the outside, in practice within the hour — and backups age out within 35 days. There is no undo, so export the workspace first (Settings → the workspace’s page) if you want the data: the export is one JSON file with the workspace’s sources, monitors, rules, destinations, incidents and events, and no secrets in it.
A workspace that has other members refuses to go with a departing account: its last owner is told to hand it over or empty it first, so nobody loses their data to somebody else’s leaving. The last instance administrator cannot close their account either.
Secrets never in logs
Section titled “Secrets never in logs”Every credential type in InfraInbox — ingest keys, session tokens, destination secrets, the master key — is wrapped in a type that refuses to print or serialize its own value; an accidental %v or a JSON encoding of it comes out as a redacted placeholder, not the secret. Error messages that touch a key or a URL with embedded credentials are built without ever including the value itself. The bundled Caddy configurations (see TLS and reverse proxies) mask the same secret shapes — the /hooks/{integration}/{secret} path and API key headers — in their own access logs, since a proxy in front of InfraInbox logs the request before InfraInbox ever sees it.
Security headers and CSP
Section titled “Security headers and CSP”Every response carries X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, and Strict-Transport-Security once you’re on HTTPS. The dashboard itself is served under a strict Content-Security-Policy with no unsafe-inline and no unsafe-eval: scripts and styles are allowed only with a fresh per-request nonce, framing from another site is refused outright (frame-ancestors 'none'), and DOM-based script injection is additionally constrained by Trusted Types. There’s no CORS header anywhere on the API — a browser on another origin can’t even attempt a cross-origin call, cookie or not.
Reporting a vulnerability
Section titled “Reporting a vulnerability”Found a security issue? Please report it privately to [security contact] rather than opening a public issue, so a fix can go out before the details do.