Security model

A disaster recovery platform holds a copy of everything worth stealing. It has to be defensible on its own terms, not on the strength of the network around it.


Agent to control plane

The agent-to-backend channel is mutually authenticated. Both ends present certificates issued by a CA you control, and either end refuses an unauthenticated peer rather than falling back to something weaker.

Certificates are rotated by reissuing the leaf certificates against the same CA, so rotation does not mean re-enrolling every host. Self-hosted deployments run their own PKI; there is no vendor-held key that unlocks your estate.

The agent connects outbound only. Nothing in the protected network has to accept an inbound connection for backups to run.

No licence, no agents

The deployment is licensed offline. A signed .lic file is uploaded through the console and verified against Ed25519 keys compiled into the binary — the deployment never contacts a licensing server, which is what makes an air-gapped estate a supported configuration rather than an exception.

Without a valid licence the platform serves no agent at all: the agent channel refuses every call and enrolment is declined. The console stays up, because that is where the replacement licence is installed — and the licence endpoint is deliberately never licence-gated, so an expired term is a thing you can fix rather than a lockout.

Credentials

Database, device and cloud credentials are held in Vault, not in the platform's own tables. Secret-typed values are never read back by the API: an operator replacing a rotated key can see which client id is installed, never the secret beside it, and an update that omits a secret keeps the stored value rather than clearing it.

For cloud accounts the default way in stores no long-lived secret at all — an AWS instance role, or an Azure managed identity. Assumed roles, access keys and app registrations are supported for the estates that need them, but the option most operators will take is the one with nothing to leak or expire.

Tokens that start code

A runbook step runs as root on a machine holding your data, so the inbound trigger token that can start one is treated accordingly. It travels in a request header and never in a URL, because a URL is logged by every proxy, load balancer and CI system it passes through. It is hashed with SHA-256 at rest, shown exactly once at the moment it is minted, and audited on every use. There is no path that reads it back; a lost token is rotated, not recovered.

A wrong token, a disabled trigger and a deleted trigger all answer identically, so a caller holding a bad token learns that it is bad and nothing about what exists. The tenant is resolved from the trigger the token belongs to, which is what stops a token issued for one customer starting a run in another.

Variables reach a step in a private file that is removed with its working directory — never on a command line, where the host's process table would show a database password to every user on the machine.

For Slack and Teams webhooks the URL is the credential — anyone holding it can post into your channel as us — so it is never returned in full by the API and never written to the audit trail.

Public endpoints carry no credentials

Two endpoints are deliberately unauthenticated: the health probe and the translation catalogue. They are also the only two, and the console is built so that no credential is ever attached to them.

That is a security property with an operational payoff. The health probe is what distinguishes "the backend is unreachable" from "your session expired" — which is precisely the distinction you need at 3am, and precisely the one that disappears if the probe itself requires a token.

Immutability and retention

Immutability is a property of the policy, not a setting somebody remembers to tick. A point written under an immutable policy cannot be altered before its retention expires — which is the property that matters when the thing you are recovering from is an attacker who reached your infrastructure rather than a failed disk.

Retention is enforced by the platform and expressed in the policy, so "how far back can we go for this asset" has one answer and it is written down.

Access

Operator actions require an authenticated account with an appropriate role. Reading into an archive — listing a recovery point's contents, extracting a file — requires project or platform administrator rights, because inspection is by design a read of the protected data itself and should not be available to anyone who can merely see that a job succeeded.

Demo sessions are read-only and never touch a real backend.

Audit

Every operator action and every run is recorded: what ran, against which asset, with what outcome, initiated by whom, and when. It is queryable per database, per device, per cloud account and per job. The trail is written continuously, which is the only way it is ever useful — an audit log started in response to an incident documents the response, not the cause.

Notification deliveries are a table too, with the rendered body stored verbatim — so did we tell anyone, and what did we say? has an answer that does not depend on what this build would render today.

Data residency

Recovery points land in storage you nominate: your bucket, your region, your lifecycle policy. In the self-hosted deployment nothing leaves your perimeter at all, which makes air-gapped estates a supported configuration rather than an exception.

Reporting a vulnerability

Mail contact@daydev.org. We would rather hear it from you than read it somewhere else.

The DR platform · Recovery automation · Deployment options