Security model
MZPanel is built so the customer VPS opens no inbound ports, the browser talks to the control plane over a normal authenticated session, and the control plane never stores more secrets than it strictly needs. This page explains the three auth layers, how agent tokens work, how offsite credentials stay sealed, and what the audit log actually records.
Three auth layers
Section titled “Three auth layers”| Layer | From → to | Mechanism |
|---|---|---|
| A | You → dashboard | Session cookie mz_session — httpOnly, SameSite=Lax, Secure |
| B | Dashboard → API | The same session cookie, plus a CSRF token on every write |
| C | Agent → API (WS) | Authorization: Bearer <agent_token> on the WebSocket upgrade |
Each layer is independent: a leaked cookie can’t drive an agent, and a leaked agent token can’t sign into your account.
Layer A — you sign into the dashboard
Section titled “Layer A — you sign into the dashboard”You sign in with a magic link (a one-time email link) or with Google /
GitHub OAuth. On success the API sets an opaque session token in the
mz_session cookie — an ID that points at a row in the sessions table, not a
JWT. Signing out revokes that row, so the cookie is dead the moment you log out.
The cookie is httpOnly (JavaScript can’t read it), SameSite=Lax, and Secure
in production.
Layer B — the dashboard calls the API
Section titled “Layer B — the dashboard calls the API”The SPA rides the same mz_session cookie for every request. On top of that,
any state-changing call (POST/PUT/PATCH/DELETE) must present a CSRF
token: a non-httpOnly mz_csrf cookie the SPA reads and mirrors back in an
X-CSRF-Token header (double-submit). A cross-site page can’t read your cookie,
so it can’t forge a matching header — the request is rejected. Idempotent reads
(GET/HEAD) and the agent’s own endpoints are exempt.
Layer C — the agent dials home
Section titled “Layer C — the agent dials home”The agent authenticates its outbound WebSocket with a long-lived agent token,
sent as Authorization: Bearer <token> during the upgrade handshake. The control
plane hashes the token (SHA-256) and looks it up before completing the
upgrade, so a bad token gets a clean HTTP status instead of an opaque socket
close:
- 401 Unauthorized — unknown or missing token → the agent keeps retrying.
- 403 Forbidden — the server is suspended → the agent retries (recoverable).
- 410 Gone — the server was deleted → the agent self-uninstalls.
Because the connection is outbound only, there is no listening port on your VPS for an attacker to reach. See The agent for the full connection model.
Agent tokens & registration
Section titled “Agent tokens & registration”The agent’s very first contact uses a short-lived install token, not the long-lived one:
- When you add a server, the panel mints a one-time install token with a
1-hour TTL and wraps it in the
curl … | sudo … bashone-liner. It’s single-use and safe to paste into a command line. - On the box,
install.shposts that install token to/v1/agent/register. The control plane verifies it (not expired, not used, not revoked), creates the server row, and returns a long-lived agent token that authenticates every future WebSocket connection. - Only the hash of each token is ever stored server-side. The raw install token is shown once, at creation, with no recovery path — if you lose it, mint a new one. You can also revoke an unused install token from the panel before it expires.
Credential sealing (zero-knowledge)
Section titled “Credential sealing (zero-knowledge)”When you store credentials for an external destination — an offsite backup bucket (S3 / R2 / B2), a cloud drive, and similar — MZPanel acts as a blind broker: it keeps only ciphertext it cannot read.
Each agent generates a keypair on the box and publishes its public key. When you enter a destination secret in the dashboard, the browser encrypts it (RSA-OAEP + AES-256-GCM) to every server that published a key, and uploads only the sealed blob. The plaintext never touches the control plane; only an agent holding the matching private key can open it. The same scheme protects the SSH vault used for agentless (SSH-only) servers.
Audit log
Section titled “Audit log”Every meaningful control-plane action is appended to an immutable audit log:
who (user + org), when, the action (a dotted name like
server.added, auth.login.success, job.dispatched), the target
(server / site / job), the caller’s IP and user-agent, and a small meta
blob. It’s the system of record for what happened across your fleet — far more
durable than shell history.
mz on the box is root-trust
Section titled “mz on the box is root-trust”The three layers above govern the panel. Once you SSH into a VPS and run the
mz CLI directly, you’re operating with local root
trust: mz runs as root and has no tier gate and no per-user RBAC — it’s
the raw engine the panel drives on your behalf. Team roles, plan quotas, and the
audit trail are control-plane concepts; they don’t apply to someone who already
has a root shell on the box. Treat SSH access to the VPS as equivalent to full
control of everything on it.
Gotchas & troubleshooting
Section titled “Gotchas & troubleshooting”- Install tokens expire in an hour. If the one-liner sits unused for too long,
registration returns
bad_token— mint a fresh server entry and paste the new command. - A suspended server keeps retrying; a deleted one uninstalls. Suspension
(
403) is recoverable — the agent reconnects once the block clears. Deletion (410) is terminal and tells the agent to remove itself, so don’t delete a server you only mean to pause. - Sealed credentials need at least one online agent’s key. If no server has published its public key yet (for example, before any agent has connected), the dashboard can’t seal a destination secret. Connect an agent first.
- Losing a token means minting a new one. Because only hashes are stored, there’s no “show me the token again.” Revoke and re-create instead.
Related
Section titled “Related”- The agent — the outbound, no-inbound-port connection model.
- Architecture — how the browser, control plane, and box fit together.
- Tiers & quotas — what plan enforcement gates, and where.
- The mz CLI — the on-box engine and its local-trust model.