Security
Handshake and encryption
Every HiveMind connection begins with a handshake that establishes a shared session key without transmitting that key over the wire. The standard handshake (protocol v1) uses PBKDF2-SHA256:
Server → Client HELLO (node_id, server public key — plaintext)
Server → Client HANDSHAKE (capabilities: binarize, crypto_required, etc. — plaintext)
Client → Server HANDSHAKE (binarize flag + PBKDF2 envelope — plaintext)
Server → Client HANDSHAKE (PBKDF2 envelope — plaintext)
Client → Server HELLO (session data, site_id, client public key — ENCRYPTED)
The PBKDF2 handshake works as follows:
- Each side generates a random IV and computes
HSUB = PBKDF2(password, IV, 100_000 iterations, SHA-256) - Both sides exchange their
HSUBandIV - Each side verifies the other's
HSUBby recomputing it locally - A common salt is derived:
salt = IV_client XOR IV_server - The session key is derived:
key = PBKDF2(password, salt, 100_000 iterations, SHA-256)— 256 bits
After the handshake all messages are encrypted with AES-256-GCM using the derived session key. Each message carries a unique 12-byte nonce and a GCM authentication tag.
An alternative v0 path skips the handshake and uses a pre-shared crypto_key directly (the legacy Encryption Key printed by add-client). This path is kept for backward compatibility only.
PGP identity (asymmetric)
Each node maintains a PGP key pair stored in ~/.config/hivemind/HiveMindComs.asc. The public key is exchanged in the encrypted HELLO after the handshake. PGP keys are used for:
- INTERCOM — encrypting point-to-point messages so intermediate nodes cannot read them
- Node authentication — verifying sender identity on INTERCOM messages via signature
Reset your PGP keys at any time with hivemind-client reset-pgp.
Identity file
The identity file at ~/.config/hivemind/_identity.json stores all credentials a satellite needs to connect:
| Field | Description |
|---|---|
access_key |
Unique access credential assigned by the hub |
password |
Used for PBKDF2 key derivation during handshake |
default_master |
Hub host address |
default_port |
Hub port (default 5678 for WebSocket) |
site_id |
Physical location identifier injected into OVOS context |
public_key |
PGP public key (ASCII-armored) |
secret_key |
Path to PGP private key file |
Write the identity file:
hivemind-client set-identity \
--key <access_key> \
--password <password> \
--host <hub_host> \
--port 5678 \
--siteid living-room
Permissions
Permissions in HiveMind are configured per client — there are no roles. Each client has its own allowed_types whitelist and per-client skill/intent blacklists.
How the policy chain works
For every message received from a satellite, the hub runs a policy admission chain:
-
allowed_typescheck — the client's per-client whitelist is checked first. If the OVOS message type is not in the allowed list, the message is dropped immediately. This is fail-closed: a new client with an empty whitelist can send nothing. -
Policy plugins — configured policies run in order. The default policy is
OVOSAgentPolicy, which readsClient.metadatato build per-client session blacklists (skills and intents).
Admin flag (make-admin) does not bypass the allowed_types check. It signals to policy plugins that extra-privileged operations are permitted, but the hard ACL is always enforced first.
Managing permissions via CLI
# Allow a message type
hivemind-core allow-msg "speak" --node-id 2
# Remove an allowed message type
hivemind-core blacklist-msg "speak" --node-id 2
# Allow/deny ESCALATE from a client
hivemind-core allow-escalate --node-id 2
hivemind-core blacklist-escalate --node-id 2
# Allow/deny PROPAGATE from a client
hivemind-core allow-propagate --node-id 2
hivemind-core blacklist-propagate --node-id 2
# Blacklist a skill (OVOS-policy)
hivemind-core blacklist-skill "skill-homeassistant.openvoiceos" --node-id 2
# Un-blacklist a skill
hivemind-core allow-skill "skill-homeassistant.openvoiceos" --node-id 2
# Blacklist an intent (OVOS-policy)
hivemind-core blacklist-intent "HomeAssistant.DeviceControllerIntent" --node-id 2
# Un-blacklist an intent
hivemind-core allow-intent "HomeAssistant.DeviceControllerIntent" --node-id 2
# Grant admin flag
hivemind-core make-admin --node-id 2
# Revoke admin flag
hivemind-core revoke-admin --node-id 2
# Set arbitrary metadata (read by policy plugins)
hivemind-core set-metadata --node-id 2 --key role --value guest
Per-client defaults
When a new client is added via add-client, it starts with a minimal default set of allowed message types. You must explicitly allow-msg any additional message types the client needs. This deny-by-default posture ensures that compromised credentials give an attacker minimal capability.
Transport security (TLS)
HiveMind can use TLS for the WebSocket transport:
hivemind-core listen --ssl true --cert_dir /path/to/certs --cert_name mycert
For local networks: a self-signed certificate is sufficient. Pass --selfsigned on satellite commands to accept it.
For internet-facing deployments: use a reverse proxy (nginx, Caddy, Traefik) with valid certificates from Let's Encrypt. Keep HiveMind on an internal port and expose only the proxy externally. Do not expose port 5678 directly to the internet.
Security checklist
Local/private networks:
- [ ] Use a strong, randomly generated password (12+ chars, mixed case and symbols)
- [ ] Store the password in an environment variable, not in plain config files
- [ ] Firewall port 5678 to trusted subnets only
- [ ] Monitor logs for repeated failed handshakes
Internet-facing deployments:
- [ ] Deploy a reverse proxy (nginx proxy manager, Caddy, Traefik)
- [ ] Obtain valid TLS certificates (Let's Encrypt)
- [ ] Enable automatic certificate renewal
- [ ] Keep port 5678 unexposed to the public internet (behind the proxy only)
- [ ] Apply rate limiting and firewall rules at the proxy
Limitations
- The password handshake provides confidentiality and mutual authentication but security is proportional to password entropy. Weak passwords are the primary attack surface.
- Without TLS, a network observer can see the encrypted ciphertext but not its content. TLS adds defence-in-depth.
- INTERCOM PGP authentication requires both nodes to have pre-exchanged public keys through a trusted channel.