Security¶
Deployer can change a server: it writes nginx sites, runs containers, issues certificates and edits Authentik. Its design assumes that whoever controls Deployer controls the server, and protects that control.
Threat model¶
| Threat | Protection |
|---|---|
| Someone on the internet reaches Deployer | Listens on loopback only; published by nginx over HTTPS. Login required for every endpoint except health, setup status, sign-in. |
| Password guessing | Argon2id (t=3, m=64 MiB, p=2); rate limits: 10 failures per IP and 5 per email per 15 minutes; constant-time dummy check for unknown emails. |
| Stolen password | TOTP two-factor (per account; mandatory with DEPLOYER_REQUIRE_MFA=1), or sign-in through your identity provider (OIDC). |
| Stolen session | Random 256-bit token, only its SHA-256 stored; HttpOnly, Secure, SameSite=Lax; 24 h sliding expiry; changing the password signs out the other devices; an admin disabling a user or resetting their password ends all their sessions. |
| Cross-site requests | CSRF double-submit token (SameSite=Strict cookie + header) on every non-GET request, plus an Origin check against the public URL. |
| A user doing too much | Roles: viewer (read), editor (build and change apps), admin (delete, users, sign-in, Authentik). Every acting request is audited. |
| Command injection | No shell anywhere. Server actions go through a fixed set of operations; names, domains, paths, compose sub-commands, service names and users are validated against allow-lists; commands run as argument vectors. |
| A malicious compose file | Lint: build: refused, paths escaping the stack folder refused, name: enforced; privileged, Docker socket, host network/PID/IPC, devices, powerful capabilities, public ports and host folders need an explicit "accept risks". |
| A broken nginx site taking down every site | Candidate sites are tested with an isolated nginx -t (temporary copy of the whole configuration) before installation; live nginx -t again before every reload; previous site restored on failure. |
| Secrets at rest | App secrets in secrets/*.env (mode 400, folder 700). In the database, sensitive values (TOTP seeds, OIDC client secret of Deployer, Authentik token, shared app passwords) are sealed with AES-256-GCM using the master key. Viewers never receive secret values. |
| Credentials in nginx sites | Sites are written with mode 600 (they may contain HTTP Basic credentials or the auto-login token). |
| Forged identity headers | nginx always sets the user headers from Authentik when enabled and clears them (Remote-User, X-authentik-*…) otherwise, so a visitor can never send them. Apps trusting headers are told to trust only the app network gateway. |
| Auto-login abuse | /__deployer/autologin is behind Authentik; Deployer's endpoint accepts only loopback requests with the app's 256-bit token (constant-time compare); the password never leaves the server. |
| Apps exposed by mistake | Ports bound to 127.0.0.1 only (Docker bypasses host firewalls for published ports); Authentik SSO is the recommended default. |
| Supply chain | A single static binary; the UI is embedded at build time; the catalog is embedded. |
Authentik permissions¶
Deployer needs an Authentik API token with administrative rights (it creates providers, applications, bindings,
scope mappings and reads users and groups). "Connect automatically" creates a dedicated service account
deployer; you can also use your own admin token. Deployer only removes Authentik objects it created (their ids
are recorded per app).
Things to know¶
- Deployer runs as root. It has to write
/etc/nginxand rundocker. Treat access to Deployer like root access to the server: enable two-factor, keep the admin list short, review the audit log. - Shared-account methods (HTTP Basic, auto-login) make everyone you let in the same user inside the app. Prefer OIDC, SAML or headers whenever the app supports them.
- The master key decrypts every sealed value. Keep it out of backups that leave the server, or encrypt them.
- Authentik capacity. Authentik keeps database connections open per login; under heavy automated login
bursts its PostgreSQL may reach
max_connections. Size it accordingly.
Reporting a vulnerability¶
Please report security issues privately to the maintainer (see the repository README) rather than in a public issue. Include steps to reproduce; you will get an answer within a few days.