Skip to content

Security model

This page describes what DumbMonit protects, who it trusts, how the design limits the damage of a mistake, and where to look in the code and tests. To report a vulnerability, follow the security policy.

DumbMonit runs inside your network. It stores the credentials it needs to read every monitor (SNMP communities, API tokens, passwords), it receives measurements from agents on your machines, and it sends requests to addresses you configure. A compromise would expose those secrets and give an attacker a foothold on the monitored network.

BoundaryWho is on the other sideTrust
Browser or script to /api/*Admins, operators and viewersAuthenticated by a session cookie or an API token; three roles limit what each may do.
Agent to /api/ingestMachines you installed an agent onA per-machine token; a leaked token can be revoked alone.
Server to monitors and servicesThird-party systemsNot trusted: responses are parsed defensively and redirects stay on the same host.
Server to notification channelsExternal servicesCredentials are encrypted at rest and never returned by the API.
Server to VictoriaMetricsA child process on loopback, or an external instanceThe embedded one has no authentication and listens on loopback; an external one is the operator’s responsibility.
/data volume and hostThe operatorTrusted. An attacker who controls the host or the volume is out of scope.
  • Least privilege. The container runs as an unprivileged user with no capability, a read-only root file system and no-new-privileges. Three roles (viewer, operator, admin) restrict what a user may change (crates/server/src/auth/users.rs; test operator_role_api.rs).
  • Secure by default. On a fresh instance only the health, setup, login and auth status routes answer until the first admin exists (test api_open_security_api.rs).
  • Economy of mechanism. One binary, one SQLite file, one loopback child process, and one way in for every client: /api/*.
  • Fail-safe defaults. A security check whose data is missing is unknown, never a pass; an unreachable monitor and a configuration error are different states.
  • Complete mediation. One authentication middleware (crates/server/src/auth/middleware.rs) guards the API routes.
  • Separation of privilege. Sessions, API tokens and agent tokens are different credentials with different reach.
  • Defence in depth. Encryption at rest on top of access control, a CSRF header on top of cookie flags, a rate limit on top of password hashing.
  • No secret round trip. The API returns has_secret and credential_kind, never the secret; omitting a secret on PUT keeps it.
  • Vetted cryptography only. Passwords are hashed with Argon2, secrets are encrypted with AES-256-GCM (crypto.rs), two-factor authentication uses TOTP with hashed single-use recovery codes, OpenID Connect uses state, nonce and PKCE. Nothing is home-made.
Class (OWASP Top 10 theme)Measures
Broken access controlOne authentication middleware; roles; a fresh instance refuses everything except setup, login, health and auth status.
Cryptographic failuresAES-256-GCM at rest, key kept outside the database; Argon2id password hashes (OWASP parameters); HTTPS on the web port itself (TLS 1.3/1.2 AEAD, X25519MLKEM768, HSTS, __Host- Secure SameSite=Strict session cookie); the agent channel is TLS 1.3 with client certificates; every connection still in clear is listed in the settings (HTTPS and encryption).
InjectionSQL goes through sqlx with bound parameters; the VictoriaMetrics proxy only forwards read queries (tsdb/mod.rs, proxy_read); parsers are unit tested.
Insecure designThe threat model above; secrets never round-trip; a single-purpose instance.
Security misconfigurationHardened Compose files, loopback-only VictoriaMetrics, operator hardening notes in the security policy.
Vulnerable componentscargo deny checks licences, bans and RustSec advisories; a weekly Trivy scan of the published images; Dependabot; GitHub Actions pinned by commit.
Identification and authentication failuresLogin rate limit per client address and per account (auth/rate_limit.rs), optional two-factor authentication, OpenID Connect and SAML 2.0 (signed assertions only), an admin can reset a user’s second factor and sign them out.
Software and data integrityAgent installers verify the SHA-256 of the downloaded binary; release images are built by CI.
Logging and monitoringA security log of sign-ins and sensitive changes (auth/audit.rs).
Server-side request forgeryIntegrations and notification channels follow redirects to the same host only, so a monitor cannot bounce a request and its API key elsewhere.
Cross-site request forgeryState-changing requests that rely on the cookie must carry X-Requested-With: DumbMonit or same-origin fetch metadata, otherwise 403; token-authenticated requests carry no cookie.

DumbMonit is a SAML service provider (crates/server/src/auth/saml). XML signature checking is delegated to the pure-Rust saml-rs crate (XML-DSig and exclusive canonicalization by bergshamra, on the aws-lc-rs engine that rustls already uses; no libxml2 or xmlsec1, and not the RustCrypto rsa crate). It is configured with its strictest policy:

  • the consumed Assertion must be covered by a verified signature: its own, or that of the root Response that contains it (Keycloak signs only the document by default). Only the signed content is read: an unsigned assertion next to a signed one, or a signed response wrapped inside another, is refused;
  • RSA with SHA-256 or stronger only (SHA-1 refused), one same-document reference, exclusive canonicalization, no other transforms;
  • verification keys come only from the certificates the administrator saved, never from the KeyInfo of the message;
  • Issuer, Audience (our entity ID), Destination and bearer Recipient (our ACS), NotBefore/NotOnOrAfter with a two-minute clock skew, InResponseTo of a request we sent (and none for IdP-initiated sign-in, off by default), a replay cache of assertion and response IDs until they expire;
  • DOCTYPE refused and parser sizes bounded before any signature work; the ACS form is limited to 256 KiB. Encrypted assertions are not supported and are refused.

The ACS (POST /api/auth/saml/acs) is posted cross-site by the identity provider, so it carries no anti-CSRF header and no SameSite=Lax cookie. It only verifies the assertion and hands a one-time ticket to GET /api/auth/saml/complete, which opens the session only in the browser that started the sign-in (a cookie bound to the RelayState). This stops an attacker from logging a victim into the attacker’s own account. The tests replay responses shaped like those of Entra ID, Okta and Keycloak, and hostile variants (unsigned, tampered, signature wrapping of the assertion or of the response, wrong audience or destination, expired or not yet valid, replayed, SHA-1), signed offline by crates/server/tests/fixtures/saml/generate.py.

Every push and pull request runs clippy -D warnings, the Rust test suite (including API, authentication and role tests under crates/server/tests/), the web tests under web/tests/, dependency checks and CodeQL analysis. The OpenSSF Scorecard runs on a schedule. Security fixes come with a regression test and are listed, with their CVE or GHSA identifier, in the release notes.

The assurance case gives the full threat model, the secure design argument and the evidence behind each claim.

  • The web port only serves HTTPS; a plain HTTP request gets a 308 redirect to the same host and port, nothing else. Reverse proxies must connect over HTTPS.
  • The self-signed certificate is not trusted by browsers: the first visit asks to compare its fingerprint. Provide a certificate for a warning-free setup.
  • Monitor options are stored unencrypted. Put secrets in credential fields.
  • The embedded VictoriaMetrics has no authentication.
  • DumbMonit is before 1.0; routes and fields can still change.