Security
Paseo follows a client-server architecture, similar to Docker. The daemon runs on your machine and manages your coding agents. Clients (the mobile app, CLI, or web interface) connect to the daemon to monitor and control those agents.
Your code never leaves your machine. Paseo is a local-first tool that connects directly to your development environment.
Architecture
The Paseo daemon can run anywhere you want to execute agents: your laptop, a Mac Mini, a VPS, or a Docker container. The daemon listens for connections and manages agent lifecycles.
Clients connect to the daemon over WebSocket. There are two ways to establish this connection:
- Relay connection (recommended), The daemon connects outbound to our relay server, and clients meet it there. No open ports required.
- Direct connection, The daemon listens on a network address and clients connect directly.
Relay connections (recommended)
The relay is the simplest way to connect from your phone. It requires no VPN setup, no port forwarding, and no firewall configuration. The daemon can stay bound to localhost or a socket file, it connects outbound to the relay, and your phone meets it there. The official relay server is the open-source Elixir service at getpaseo/paseo-relay.
Relay is off on new installations. When you pair a device from paseo, paseo daemon pair, or Paseo Desktop, Paseo asks before enabling it. Choosing not to enable relay leaves the daemon available for direct TCP, Tailscale, or other VPN connections and does not create a pairing QR code. Use --relay with the CLI pairing or startup command to opt in without an interactive prompt.
The relay is designed to be untrusted. All traffic between your phone and daemon is end-to-end encrypted. The relay server cannot read your messages, see your code, or modify traffic without detection. Even if the relay is compromised, your data remains protected.
How it works
- The daemon generates a persistent ECDH keypair and stores it in
$PASEO_HOME/daemon-keypair.json - When you scan the QR code or click the pairing link, your phone receives the daemon's public key
- Your phone sends a handshake message with its own public key. The daemon will not accept any commands until this handshake completes.
- Both sides perform a Curve25519 ECDH key exchange to derive a shared key. All subsequent
messages are encrypted with XSalsa20-Poly1305 (NaCl
box).
The relay sees only: IP addresses, timing, message sizes, and session IDs. It cannot read message contents, forge messages, or derive encryption keys from observing the handshake.
Why the relay can't attack you
The daemon requires a valid cryptographic handshake before processing any commands. A compromised relay cannot:
- Send commands, Without your phone's private key, it cannot complete the handshake
- Read your traffic, All messages are encrypted with XSalsa20-Poly1305 (NaCl
box) after the handshake - Forge messages, NaCl
boxprovides authenticated encryption; tampered messages are rejected - Replay old messages, Each session derives fresh encryption keys
Trust model
The QR code or pairing link is the trust anchor. It contains the daemon's public key, which is required to establish the encrypted connection. Treat it like a password, don't share it publicly.
If you believe a pairing offer has been compromised, restart the daemon to generate a new session ID and rotate the relay pairing.
Direct connections
By default, the daemon listens on 127.0.0.1:6767 (localhost only). This is safe for local CLI usage but not reachable from your phone or other devices.
For relay and Tailscale setup instructions, see Connectivity.
Socket file (CLI only)
For maximum isolation, you can configure the daemon to listen on a Unix socket file instead of a TCP port. This prevents any network access entirely, only processes on the same machine can connect. The CLI supports this mode, but the mobile app and web interface require a network connection.
VPN access
Use a VPN such as Tailscale when you want a direct connection outside your local network. The VPN encrypts the traffic and keeps the daemon off the public internet. Bind the daemon to its VPN address, set a Paseo password, then add that address as a direct connection in the client.
Binding to 0.0.0.0
Warning: Binding to
0.0.0.0makes the daemon reachable on all network interfaces, including public Wi-Fi and local networks. This can expose your daemon to unauthorized access. If you must bind to all interfaces, ensure you have proper firewall rules and review yourhostnamesconfiguration.
DNS rebinding protection
CORS is not a complete security boundary. It controls which browser origins can make requests, but does not prevent a malicious website from resolving its domain to your local machine (DNS rebinding).
Paseo uses a host allowlist to validate the Host header on incoming requests. Requests with unrecognized hosts are rejected.
Configure via daemon.hostnames in config.json:
- Default (
[]): allowlocalhost,*.localhost, and all IP addresses ['.example.com']: allowexample.comand any subdomain, plus defaultstrue: allow any host (not recommended)
Password authentication
By default, anyone who can reach the daemon's listening address can connect. On localhost this is fine, only local processes have access. But if you bind to a network interface (e.g. your LAN IP or 0.0.0.0), or if you don't fully trust your local network, you can require a password.
When a password is configured, all HTTP requests must include an Authorization: Bearer <password> header and all WebSocket connections must authenticate via subprotocol. Unauthenticated requests receive a 401 Unauthorized response. Only the /api/health liveness endpoint is exempt, so that process supervisors and load balancers can probe without credentials.
If you enable the bundled web UI, its static files are also served without the password so the login screen can render. This is by design, the API and WebSocket still require authentication before any agent data is returned or any command runs. Set a password before binding the daemon to a network so the data behind the page stays protected.
The password is stored as a bcrypt hash in config.json, the daemon never stores it in plaintext. See Configuration for setup instructions.
What password auth does and does not do
- Does: Prevents unauthorized clients from controlling your agents, even if they can reach the daemon over the network.
- Does not: Encrypt traffic. Password auth protects access, not confidentiality. If you need encrypted connections over an untrusted network, use the relay (which provides end-to-end encryption) or a VPN like Tailscale.
When to use it
- You want to bind the daemon to a LAN or Tailscale address and restrict who can connect.
- You don't fully trust your local network (shared office, public Wi-Fi with a VPN, etc.).
- You're exposing the daemon via a reverse proxy and want an additional authentication layer.
We still recommend the relay for mobile access, it combines authentication with end-to-end encryption out of the box. Password auth is primarily useful for direct LAN or VPN connections where you want access control without the relay.
Docker self-hosting
The official Docker image runs the daemon and bundled web UI in one container. It binds to 0.0.0.0:6767 inside the container so Docker port publishing and reverse proxies work normally.
For Docker deployments:
- Set
PASEO_PASSWORDbefore publishing the port to a LAN, VPN, or public address. - Use HTTPS at your reverse proxy for browser access outside localhost.
- Set
PASEO_HOSTNAMESfor any DNS names you use to reach the container. - Keep
/workspacemounts scoped to repositories the agents should be able to read and write. - Treat
/home/paseoas sensitive, it can contain daemon state and provider credentials.
The image runs the daemon and launched agents as the non-root paseo user, but container user isolation is not a substitute for careful mounts. Agents can still access whatever code and credentials you mount into the container.
See Docker for Compose and reverse proxy examples.
Agent authentication
Paseo wraps agent CLIs (Claude Code, Codex, OpenCode) but does not manage their authentication. Each agent provider handles its own credentials:
- Claude Code, authenticates via Anthropic's OAuth flow, stored in
~/.claude/ - Codex, uses your OpenAI API key or OAuth session
- OpenCode, configured via provider-specific API keys
Paseo never stores or transmits provider API keys. Agents run in your user context with your existing credentials.
Hub identities and credentials
Hub CLI login and daemon enrollment are separate identities. paseo hub login [origin] stores a durable organization-scoped human credential in a private file under PASEO_HOME, keyed by the normalized Hub origin. A stored credential is never sent to another origin. Protect PASEO_HOME as sensitive local state.
Hub CLI credentials are bearer secrets. Remote Hub origins must use HTTPS; cleartext HTTP is accepted only for loopback development origins (localhost, 127.0.0.1, and [::1]).
paseo hub connect [origin] uses that credential, or an explicit API key, only to request a short-lived one-time enrollment token. The daemon exchanges the token and retains its own independently generated relationship credential. Logging out of the CLI does not silently remove daemon authority. Interactive logout completes any accepted same-origin daemon disconnection before deleting the login. In JSON and noninteractive use, logout never prompts or disconnects; pass --disconnect-daemon only when automation intends to remove both identities.
--api-key and PASEO_HUB_API_KEY override stored login without being persisted. Prefer environment or secret-manager injection for automation, and avoid command-line flags when local process listings or shell history are visible to other users.
Recommendations
- Use the relay for mobile access, it's the simplest option and all traffic is end-to-end encrypted
- Treat the QR code like a password, anyone with the pairing offer can connect to your daemon
- Set a password if you bind to a network address, it prevents unauthorized clients from controlling your agents
- Never bind to 0.0.0.0 without a password, without one, any device on your network can connect
- Scope Docker mounts tightly, agents can access mounted workspaces and provider credentials
- Keep your daemon updated, security improvements are released regularly
- Protect the Hub configuration branch, push access to
.paseo/hub.ymlcontrols what that project can reach, see How Hub works