Skip to content

Assurance case

This page argues that DumbMonit meets its security requirements: it states the threats, the boundaries where trust changes, why the design limits the damage of a mistake, how each common class of weakness is countered, and what evidence backs each claim. It extends the security model, which lists the same measures more briefly. To report a vulnerability, follow the security policy.

File paths below are relative to the repository root.

  1. Only an authenticated user, with a role that allows it, may read or change the configuration, the alerts or the measurements.
  2. The credentials DumbMonit stores for your monitors are not readable from the database file, from a backup bundle without its passphrase, or from the API.
  3. Only a machine holding a valid token may feed measurements in, and a leaked token can be revoked on its own.
  4. DumbMonit cannot be turned into a way to reach services it should not, in particular its own unauthenticated VictoriaMetrics child.
  5. A page served to a browser cannot run script it did not ship with, and a third-party site cannot make a signed-in browser change anything.
AttackerCapabilityIn scope
Anonymous network clientReaches the HTTP portYes
Signed-in user with a low roleValid session or read-only API tokenYes (privilege escalation)
Malicious web pageRuns in an admin’s browserYes (CSRF, XSS, clickjacking)
Compromised or malicious monitored systemAnswers probes with hostile contentYes (parsers, redirects)
Malicious or stolen agentHolds one agent tokenYes (reach of a single token)
Admin entering a hostile URLConfigures a webhook or monitor addressYes, for the server’s own addresses
Someone with the host, the /data volume or the container runtimeFull controlNo: out of scope, as in the security policy
Vulnerabilities inside VictoriaMetrics or the monitored systemsUpstream codeNo: report upstream
BoundaryWhat crosses itHow it is guardedEvidence
Browser to /api/*Session cookie or API token, JSONOne authentication middleware, three roles, CSRF header on cookie-authenticated writescrates/server/src/auth/middleware.rs; tests auth_api.rs, operator_role_api.rs, api_open_security_api.rs, cors_policy_api.rs
Browser to served pagesHTML, script, styleContent Security Policy with a per-response nonce, nosniff, Referrer-Policy: same-origin, X-Frame-Options: DENYcrates/server/src/api/mod.rs (security headers layer)
Identity provider to serverOpenID Connect redirect and ID tokenState, nonce and PKCE (S256); local account linking is explicitcrates/server/src/auth/oidc/; test oidc_api.rs
Agent to /api/ingestMeasurements, command resultsPer-agent token, stored as a SHA-256 hash (dmon_ prefix)crates/server/src/api/ingest.rs, crates/server/src/collectors/agent/token.rs; test agent_binding_api.rs
Relay agent to serverProbe results from another networkSame agent token check (agent_commands::authenticate)crates/server/src/api/relay.rs; test relay_api.rs
Server to monitored systemsProbe requests, hostile responsesRedirects followed on the same host only, bounded redirect count, response parsers with unit tests, certificate verification on by defaultcrates/collectors/src/http.rs (same_host_redirects)
Server to notification channelsWebhook and SMTP requestsSame redirect policy plus a guard that refuses the server’s own addresses, checked before sending, on each redirect and at DNS resolutioncrates/server/src/notify/guard.rs; test notify_guard_api.rs
Server to VictoriaMetricsWrites and read queriesEmbedded child listens on 127.0.0.1:8428 by default; the Prometheus-compatible proxy relays only the read routes (query, query_range, series, labels)crates/server/src/config.rs (DUMBMONIT_VM_LISTEN), crates/server/src/api/prometheus.rs (readable_segments)
Server to SQLiteConfiguration and stateBound parameters through sqlxcrates/server/src/db/
Server to /dataDatabase, secret keySecrets encrypted with AES-256-GCM; the container user owns /data onlycrates/server/src/crypto.rs, Dockerfile

The argument follows the design principles of Saltzer and Schroeder.

  • Economy of mechanism. One binary, one SQLite file, one loopback child process, one entrance for every client (/api/*).
  • Fail-safe defaults. On a fresh instance every route under the authentication guard answers 401; only health, auth status, setup and login respond, so nobody can prepare the instance before its owner (auth/middleware.rs, test api_open_security_api.rs). VictoriaMetrics listens on loopback unless the operator changes it. TLS certificates of monitored services are verified unless the operator sets insecure_tls (crates/collectors/src/http.rs).
  • Complete mediation. The authentication middleware runs on every API route rather than each handler checking for itself.
  • Open design. The code, the API description (crates/server/src/api/openapi.yaml) and this page are public. Security does not rely on secrecy of the design.
  • Separation of privilege. Sessions, API tokens and agent tokens are different credentials. API tokens carry a read or write scope (auth/token.rs) and users have a viewer, operator or admin role.
  • Least privilege. The image runs as UID 65532 (Dockerfile). Started as root (user: "0:0", for bind mounts), the server only hands the data directory over to that user, without following links, then drops root for good and checks it cannot regain it, before opening anything else (privileges.rs). The Compose file keeps only the four capabilities this step needs, sets a read-only root file system and no-new-privileges (docker-compose.yml).
  • Least common mechanism. Agents authenticate with their own token, so revoking one machine does not touch any user or other agent.
  • Psychological acceptability. Secure behaviour is the default and needs no extra step: the cookie is HttpOnly and SameSite=Lax, the CSRF header is added by the web client, secrets are write-only in the interface.
  • Defence in depth. Encryption at rest on top of access control; the CSRF header on top of SameSite; the login rate limit on top of Argon2 hashing; a redirect policy on top of the self-address guard.
  • Passwords: Argon2 (auth/password.rs). Session and API token secrets are stored as SHA-256 hashes of random values (auth/session.rs, auth/token.rs).
  • Secrets at rest: AES-256-GCM with a fresh nonce per encryption (crypto.rs). The key is derived with Argon2id from DUMBMONIT_SECRET or the generated key file, with a persistent salt; a secret shorter than 16 characters is refused, and a canary value detects a wrong key at start.
  • Backups: the key derives from the operator’s passphrase with Argon2id, with parameters stored in the bundle, so the algorithm can change without breaking old bundles (backup/bundle.rs).
  • Second factor: TOTP, with hashed single-use recovery codes (auth/totp.rs).
  • OpenID Connect: state, nonce and PKCE S256 (auth/oidc/pkce.rs).
  • All primitives come from maintained libraries (aes-gcm, argon2, sha2); none is home-made. The built-in listener only serves HTTPS (plain HTTP gets a redirect), and cookies gain Secure (auth/cookie.rs).
Weakness (OWASP Top 10, CWE)CountermeasureEvidence
Broken access control, CWE-862, CWE-285Authentication middleware, roles, token scopesoperator_role_api.rs, api_tokens_api.rs, api_open_security_api.rs
Cryptographic failures, CWE-327, CWE-312AES-256-GCM at rest, Argon2, no secret returned by the APIcrypto.rs unit tests; has_secret / credential_kind instead of the secret
SQL injection, CWE-89Bound parameters through sqlxcrates/server/src/db/
Cross-site scripting, CWE-79Svelte escapes output; CSP with default-src 'none', scripts only from 'self' and a nonce; base-uri 'none'api/mod.rs
Cross-site request forgery, CWE-352Writes by cookie need X-Requested-With: DumbMonit or same-origin fetch metadata; CORS grants nobody the header; SameSite=Laxauth/middleware.rs, auth/cors.rs; test cors_policy_api.rs
Clickjacking, CWE-1021X-Frame-Options: DENY and frame-ancestors 'none', except on opt-in public status pages meant to be embeddedapi/mod.rs
Server-side request forgery, CWE-918Same-host redirects only; the notification guard refuses the server’s own listen and VictoriaMetrics addresses, including after DNS resolution (rebinding). Loopback and private ranges stay reachable on purpose, because homelab services live therenotify/guard.rs; test notify_guard_api.rs
Authentication failures, CWE-307, CWE-287Login rate limit with growing delay per address and per account, optional TOTP, OIDC with PKCE, admin reset of a second factorauth/rate_limit.rs, auth/totp.rs, auth/oidc/
Insecure deserialization, resource exhaustion, CWE-400Typed JSON via serde, request bodies capped at 16 MiB, bounded redirectsapi/mod.rs, collectors/src/http.rs
Memory safety, CWE-787, CWE-416Rust; unsafe appears only in the Windows agent’s performance counter code (crates/agent/src/collect/perf_counters.rs)grep for unsafe
Vulnerable components, CWE-1104cargo deny (licences, bans, advisories); Dependabot; Trivy scan of the published images; actions pinned by commit; --locked builds.github/workflows/ci.yml, security.yml, .github/dependabot.yml, deny.toml
Software and data integrity, CWE-494Agent installers verify the SHA-256 of the downloaded binarycrates/agent/install/install.sh, install.ps1
Logging and monitoring failures, CWE-778Security log of sign-ins and sensitive changesauth/audit.rs; test security_audit_api.rs

Known residual risks are listed in the security model (self-signed default certificate, unencrypted monitor options, unauthenticated embedded VictoriaMetrics). The CSP allows inline styles (style-src 'unsafe-inline'), which weakens it against style injection but not against script execution. One accepted advisory exists, RUSTSEC-2023-0071, ignored in deny.toml with its reason: the rsa crate verifies RS256 signatures with the provider’s public key and never uses a private key.

  • Continuous integration (.github/workflows/ci.yml): cargo fmt --check, clippy -D warnings, the full Rust test suite, cargo deny check licenses bans and cargo deny check advisories, web type check, web tests and build, and a build of the Docker image, on every push and pull request.
  • Scheduled scans (.github/workflows/security.yml): Trivy on the published server and agent images, and the OpenSSF Scorecard, weekly and after each :edge publication. Results appear in the repository Security tab, where GitHub code scanning (CodeQL) reports as well.
  • Tests: API, authentication, role, CORS, OIDC, guard and audit tests under crates/server/tests/, unit tests next to the code, and web tests under web/tests/. Rust line coverage measured with cargo-llvm-cov is 91 %.
  • Regression policy: a fix comes with a test that fails without it (see the Test policy in CONTRIBUTING.md).
  • Public record: security fixes are published as advisories and listed in the release notes.