Security model

The design assumes the web interface will be attacked and limits what a successful attack could do.

Two halves

respirecloudd serves the panel and the API as an unprivileged system user. Everything that needs root is done by respirecloud-agent, which accepts only typed operations declared in module manifests. Operations carry validated parameters and run programs as argument lists; there is no shell string anywhere for input to break out of. The panel cannot ask the agent to "run this command".

  1. Browser, CLI or API token

    Signs in with a passkey or a scoped token. Every method and path is denied unless an action allows it.

  2. respirecloudd unprivileged user

    Checks permission, plan limits and risk; validates parameters against the action's schema; writes the audit row.

  3. Bus TLS 1.3, per-server keys

    Each server can only receive its own operations. A compromised server cannot see another's.

  4. respirecloud-agent root, minimal

    Runs typed operations from module manifests only. Commands are argument lists, never shell strings.

Servers and the bus

  • Agents dial out to the panel over TLS 1.3 with the panel's certificate authority pinned. It works through NAT; no inbound port on the server is needed.
  • Each server is enrolled with a one-time token (valid one hour, stored only as a hash) and generates its own key, which never leaves the machine.
  • Each server may publish only its own heartbeat and receive only its own operations. Everything else on the bus is denied by omission, so a compromised server cannot read another server's work.
  • The enrolment endpoint is rate-limited per address and every attempt is audited without the token.

Modules

  • Each module runs as its own process and is launched only after its checksum is verified.
  • Each module owns one database schema through its own database role, and reaches other modules only through their actions.
  • Parameters are validated against strict schemas by the host before the module sees them. Unknown fields are refused.
  • Agent-side code re-validates anything that reaches a server (nginx snippets, compose files, firewall rules), so a compromised control plane still cannot push something the agent would refuse.

Signing in

  • Passkeys (WebAuthn), passwords hashed with Argon2id, authenticator codes and one-time recovery codes.
  • Two-step sign-in is required for administrators and resellers.
  • High-risk and critical actions need a fresh confirmation; the session is rotated when it is given.
  • Sessions are listed per device and can be ended at once. Suspending a person ends their sessions and tokens in the same transaction.
  • Lockout and throttling on repeated failures. Browser calls need a CSRF header on top of the session cookie.
  • Acting as a customer requires a reason and a fresh confirmation, is time-limited, cannot change their credentials, and is audited under both identities.

Trusted browsers and the PIN

A person can keep one browser signed in and come back with a short PIN. The browser is proven by two halves that are useless apart: a non-extractable ECDSA P-256 key kept in the browser, whose public half the panel stores, and a __Secure-rc_device cookie (HttpOnly, Secure, SameSite=Strict) of 256 random bits, of which only the hash is stored.

  • A stolen cookie without the key, or a copied key store without the cookie, gets nothing. Coming back needs a signature over a fresh server challenge that lives 60 seconds and works once.
  • The cookie changes at every visit. Presenting an old value removes the browser, ends its sessions and tells the person.
  • A browser that has come back stays locked, able to call nothing, until the PIN is right. Idle sessions lock instead of ending.
  • The PIN is hashed with Argon2id. The fifth wrong PIN removes the browser; ten across a person's browsers in an hour remove all of them.
  • A PIN is never a step-up factor: high-risk actions still ask for a passkey, an authenticator code or the password.
  • Trust lasts 30 days by default (90 at most) from the day it was given. A password change, a suspension, removing the PIN or a policy switch removes it. Impersonation, API tokens and the phone app are never trusted.

Updates and downloads

  • A release is checked against a key built into the program: the manifest's signature, the archive's size and hash. A release program cannot be pointed at another key by editing a file.
  • The unprivileged panel downloads and checks a release. The root agent trusts nothing it staged: it copies, checks again with its own key, and only then runs the installer.
  • Before it installs, the panel keeps the running release and a copy of its database. If the new release does not come up healthy, both are put back.
  • Each installation has its own key, made on the server. Requests to the licence service are signed with it, so a copied licence or key file is worthless on another server.
  • Releases and Store add-ons are served only through short-lived links, and only to a licence that is active or in grace.
  • Permissions can be scoped to one resource, and a scoped deny wins over every allow, for administrators too.

Secrets

Credentials for storage connections, backup repositories, DNS servers, registries and stored database passwords live in the panel's vault, not in module tables, logs or events. Private keys for TLS and DKIM are generated on the server that uses them. Passwords, tokens and keys are redacted from the audit log and masked in shipped logs.

The audit chain

Every change is written with the actor, the person they were acting as, the source and the parameters, and each row carries the hash of the one before it. A scheduled check recomputes the chain and raises an event on the first row that does not match. No action edits or deletes audit rows. Signed checkpoints are kept by the root agent outside the database, so removing the newest rows is detected, and every checkpoint can also be copied off the server by syslog or a signed webhook. A screen compares a pasted copy against the log; a rewrite of both the database and the local log is caught only by that outside copy.

Customer isolation

  • Every hosting account is a Linux user with its own systemd slice (cgroup v2 limits) and its own PHP-FPM masters.
  • File operations run in a helper that is the account's user; paths are resolved with openat2(RESOLVE_BENEATH), so escapes through symlinks or .. are refused by the kernel.
  • nginx refuses to serve symlinks to files owned by someone else; per-account PHP sockets cannot be reached by other accounts.
  • Metrics and logs are separated by the stores themselves: one log tenant per account, and metric queries forced to the account's own series.
  • Containers run with dropped capabilities, no-new-privileges, user-namespace remapping and the account's limits; compose files are rebuilt from an allow-list.

Untrusted input

  • The browser terminal needs a single-use, 30-second ticket bound to your browser session, a matching Origin and the session cookie; it re-checks the session every 15 seconds.
  • Webmail rebuilds every HTML message from an allow-list, blocks remote content by default and is tested against a corpus of XSS vectors and a fuzzer. The mail screens will add a sandboxed frame as a second layer.
  • Archives are extracted with size, count and ratio limits and never create links or devices.
  • Reverse-proxy sites cannot point at private addresses unless an administrator allows it.

Reporting a vulnerability

Please email info@respiresoft.com with the subject "RespireCloud security report". Please do not test against servers you do not own. More in our security overview.