Security model

How PearDock authenticates peers: keys in .env, default viewer, admin seed proof, pd1. invites, roles on every RPC method, and revoke. Also: threat model summary and hardening checklist.

1. Overview

PearDock is a self-hosted P2P Docker control plane. There is no central login. Anyone who knows the server public key can attempt a HyperDHT dial. That alone must not mean full Docker control — so the default role is viewer (read-only).

[Desktop client] --Noise/HyperDHT--> [peardock-server] --unix socket--> [dockerd]
                                          |
                                          +-- /opt/peardock/.env
                                          +-- peardock-peers.json
                                          +-- peardock-vault.json
                                          +-- peardock-audit.log

Three legitimate ways in:

Paste in Add peerAccess
Public key only (SERVER_PUBLIC_KEY)Viewer (read-only)
Public key + SERVER_SEEDAdmin (seed never sent on the wire)
Full pd1.… inviteRole in the invite — no seed sharing

Canonical long-form doc in the source tree: docs/SECURITY_AUTH.md.

2. Keys live in .env

After the first successful start, identity is written to /opt/peardock/.env (mode 600). Check that file:

sudo grep -E '^(SERVER_PUBLIC_KEY|SERVER_SEED)=' /opt/peardock/.env
# or: sudo cat /opt/peardock/.env
VariableWhoNotes
SERVER_PUBLIC_KEY Safe to share for read-only How clients dial the server over HyperDHT
SERVER_SEED Admins only Server identity + HMAC MAC key + vault material. Never give to operators.

From the seed, PearDock derives a MAC key (HKDF). That key signs admin proofs and capability grants. The seed itself is never transmitted on the wire. Rotating the seed changes the public key; all clients must re-add the peer.

Each desktop client also keeps a stable DHT keypair (peer id). The server uses that peer id for registration, reconnect, allowlist, and revoke.

3. Roles

Order: viewer < operator < admin.

Every RPC method has a minimum role (MethodRoles). Unknown methods default to admin (fail closed). The UI may hide buttons; the server is authoritative.

RoleTypical powers
viewer List/inspect, logs, stats. No mutate. Cannot manage invites or ACL.
operator Day-to-day Docker mutate (start/stop, pull, stacks, many Swarm ops, terminals). Still cannot mint invites.
admin Everything + invite/revoke, vault, destructive prune paths, unrestricted CLI if enabled.

Invite management is admin-only: invitePeer, listInvites, deleteInvite, revokePeer, unrevokePeer, clearRevokedPeers.

4. Handshake elevation

Baseline role (usually viewer) is resolved from env / admin keys / peer policy. Then handshake may elevate:

  1. adminProof — seed ownership → admin
  2. capability — HMAC grant from a pd1. invite (or direct token)
  3. Otherwise stay at baseline
!
Fail closed. If the client presents a capability that is invalid, spent, or expired, and the peer is not already registered as elevated, the handshake fails. It does not silently succeed as viewer.

If a capability fails but this client identity is already registered as operator/admin, reconnect can use the registered role so restarts keep working.

Optional: PEARDOCK_ADMIN_KEYS (fixed admin peer ids), PEARDOCK_DEFAULT_ROLE, PEARDOCK_PEER_ALLOWLIST=1, PEARDOCK_INSECURE_OPEN_ADMIN=1 (dev only — everyone is admin).

5. Admin seed proof

Goal: prove “I know SERVER_SEED” without sending the seed over the network.

  1. In Add peer, paste public key + SERVER_SEED (from .env).
  2. Client derives the same MAC key and builds an HMAC over peardock-admin-v1 || nonce || peerId || serverPublicKey.
  3. Server verifies with its seed-derived key (constant-time compare).
  4. Success → role admin. Failure → ADMIN_PROOF_FAILED.

Best practice: only true admins hold the seed. Onboard everyone else with invites. Prefer PEARDOCK_ADMIN_KEYS for fixed admin machines if you do not want to paste the seed on every client.

6. pd1 invites & capabilities

Invites do not use Autopass or RocksDB. They are pure HMAC capability grants packaged as a self-contained share string.

Capability token

base64url(JSON payload) + "." + base64url(HMAC-SHA256(macKey, payload))

Payload includes role, jti (unique grant id), exp (or null = never), optional peer bind, and issued-at.

pd1 package

pd1.<base64url JSON { publicKeyHex, capability, role, jti, ... }>

Operators paste the full string in Add peer (never truncate). The client decodes public key + capability and dials with the grant at handshake.

Mint (admin)

  1. Connect as admin (key + seed).
  2. Access → Create invite (role usually operator).
  3. Default: never expires, unlimited uses (persistent). Optionally set TTL / max uses.
  4. Copy the full pd1. string and share it (not the seed).

Redeem (operator)

  1. Paste full pd1.… in Add peer.
  2. Client dials embedded public key and presents the capability.
  3. Server verifies HMAC, then registers the client peer id with the grant role.
i
Delete invite vs kick a person. Deleting an invite stops new redemptions of that grant id. An already-registered operator keeps reconnecting until you revoke their peer id.

7. Registration, reconnect, revoke

State lives in peardock-peers.json (override with PEARDOCK_PEER_POLICY):

After a successful elevate, reconnect can use the registered role even if that invite jti is later deleted. To remove access for a lost laptop: Access → revoke that peer. Unrevoke / clear revoked are admin recovery tools.

Common error codes: CAPABILITY_INVALID, CAPABILITY_EXPIRED, CAPABILITY_SPENT, ADMIN_PROOF_FAILED, PERMISSION_DENIED. If a saved peer still holds a stale spent capability, paste a new full pd1 invite or clear the cached grant.

8. Workflows

Single admin

  1. Install server; read keys from /opt/peardock/.env.
  2. Add peer with public key + SERVER_SEED → admin.

Multi-operator (recommended)

  1. Admin connects with seed proof.
  2. Access → Create invite → share full pd1. string only.
  3. Operator pastes invite → operator role; peer registered.
  4. Lost device → revoke peer id. Leaked unused invite → delete invite, mint new one.

Viewer guest

Share only SERVER_PUBLIC_KEY. Observe-only.

9. Threat model (summary)

Assets

AssetSensitivity
SERVER_SEEDCritical: identity and vault key derivation
Docker socket accessCritical: full host container control
Registry passwords (vault)High: encrypted at rest
HMAC capability grants / pd1. invitesMedium: elevated access without seed
Audit logMedium: forensic integrity
Container data / env secretsHigh: via inspect, logs, exec

Adversaries

  1. Public key only: viewer (read-only).
  2. Stolen pd1 invite: can elevate until delete/spend/expire; revoke peer after redeem.
  3. Compromised client: holds that role until revoke.
  4. Local filesystem on server: can steal seed if .env perms are wrong.
  5. Malicious container: out of scope for PearDock; Engine isolation applies.

Controls

ControlMechanism
Transport E2EHyperDHT Noise
Capability ACLviewer / operator / admin + MethodRoles
Default roleviewer; elevate via seed HMAC or pd1 invite
Admin proofHMAC-SHA256 from SERVER_SEED (seed never on the wire)
pd1 invitesSelf-contained public key + capability (no vault DB)
Peer policyRegister, revoke, jti spend, optional allowlist
Audit / rate limitPrivileged method log; per-peer limiter
Registry vaultAES-256-GCM (Bare-safe KDF); UI under Registry
Browse FS / tunnelsDefault-deny browse; loopback / allowlisted tunnel targets

Residual risks

10. Operator hardening checklist

Install and multi-op steps: Operator guide · Quick start · RPC & API