The trust model
Mangrove runs code you trust, on a box dedicated to it. It isn’t a sandbox for strangers’ code.- Docker access is root access. The
mangroveuser is in thedockergroup, so anyone who controls Mangrove, or any process running asmangrove, controls the box. That’s unavoidable for a Docker-based platform, and why the box should do nothing else. - Apps run as ordinary containers. Containers share the host kernel. There’s no gVisor or Kata isolation. A container escape is a box compromise.
- An owner account is full trust. Owners can read NAS share passwords, run commands in any container, and download a backup containing every secret.
What’s isolated
Mangrove itself. The systemd unit runs it as an unprivileged user, withProtectSystem=strict, ProtectHome=true, NoNewPrivileges=true, a
private /tmp, and write access only to its two data directories.
Mangrove’s API. It listens on 127.0.0.1 only. Expose it deliberately,
over HTTPS. See Going to production.
Apps from the network. Containers’ ports are never published on the
box. Only Caddy forwards to them, and only for public services.
Internal-only services, such as databases, have no route at all. The one
exception is a NAS share’s SMB port.
Apps from each other: not isolated. Every app is on the same Docker
network and can reach every other app’s internal port. Rely on each
service’s own authentication (database passwords and so on), not the
network.
Memory. Each container has a memory and CPU limit, and in production
all app containers together sit in a slice with a hard memory cap, so one
runaway app can’t starve SSH or Mangrove. See
Scaling and resources.
Drive mounting. Mounting needs root, so it’s done by a separate small
helper, mangrove-mountd, which can only list, mount and unmount
removable drives and refuses to touch the system disk. Mangrove talks to it
over a Unix socket and never gains mount privileges itself. See
storage.md.
Secrets
- Encrypted at rest with AES-256-GCM under
master.key: secret variables, GitHub tokens, webhook secrets. Each secret is bound to the record it belongs to, so a ciphertext can’t be moved to another record. - Write-only in the API and dashboard. A secret variable’s value is never returned after it’s saved.
- Decrypted only to use them: injecting variables into a container, cloning a repo, checking a webhook signature.
master.keyplus the database is everything. Protect backups accordingly. See Backups and restore.
Accounts and sessions
- Account passwords are hashed with argon2id (minimum 8 characters). Deployment gate passwords are hashed with bcrypt.
- Sessions last 30 days in an
HttpOnly,SameSite=Laxcookie. Serve the dashboard over HTTPS; the cookie isn’t markedSecureby Mangrove. - Sign-in and setup are limited to 5 attempts per 5 minutes per IP.
- Owners can see and revoke every session.
- Workspace roles are enforced by the API on every request, not by the dashboard. See Workspaces and roles.
Inbound webhooks
The GitHub webhook endpoint requires no sign-in but checks every delivery’s HMAC signature with a constant-time comparison before doing anything, and ignores replays of a delivery it has already seen.Protected deployments
The password gate runs in Mangrove, in front of the app. Gate cookies and account handoff tokens are signed and short-lived, and bound to one deployment. Paths in the “unprotected pages” list can’t be abused with.. or // tricks. See Protection.
The MCP server
The remote (HTTP) MCP transport is guarded by a secret token in its URL, not OAuth. Anyone with the URL can redeploy, roll back and run commands. Treat it like a password, and rotate it by changingMANGROVE_MCP_URL_TOKEN. The MCP server deliberately has no tools for
deleting things, managing users or reading secrets. See
MCP for agents.
Known gaps
- GitHub tokens are shared across the whole install, not scoped per workspace.
- NAS share passwords are stored unencrypted.
- There’s no two-factor authentication, single sign-on, or API token with limited scope.