Skip to content

Securing hal0

hal0 has no built-in authentication CLI, no user/password store, and no bundled reverse proxy. hal0-api binds 0.0.0.0:8080 and every endpoint is reachable by anything that can reach that port. This was a deliberate cut (ADR-0012, “remove-auth-and-caddy”) — earlier betas bundled a password store and a Caddy TLS-terminating proxy, and both were removed because they duplicated, badly, what a real reverse proxy already does better. There is no --auth=basic install flag and no hal0 auth subcommand.

hal0 assumes one of two deployment postures:

  • Single trusted LAN — hal0 is reachable only from machines you trust (e.g. your home network), and an open API is an acceptable risk.
  • Reverse proxy at the edge — you put a proxy you control (Caddy, Traefik, nginx, a cloud load balancer, a tunnel) in front of hal0, and that proxy — not hal0 — owns TLS termination and authentication.

There is no third, hal0-managed option. If you want a login prompt, HTTPS, or per-request access control, that’s a proxy you stand up and run yourself.

Reverse proxy at the edge (your proxy, not hal0’s)

Section titled “Reverse proxy at the edge (your proxy, not hal0’s)”

Run hal0 bound to 127.0.0.1 (or a private interface) and put a proxy in front of it that terminates TLS and enforces auth before forwarding to hal0. None of the following is installed, managed, or aware of hal0 — these are plain examples of proxies you configure and operate.

hal0.example.com {
basic_auth {
alice $2a$14$... # bcrypt hash from `caddy hash-password`
}
reverse_proxy 127.0.0.1:8080
}

Caddy obtains and renews the certificate automatically via ACME as long as port 80/443 are reachable for the challenge. Generate the bcrypt hash once with caddy hash-password — Caddy never sees the plaintext password again.

  1. TLS termination — present a certificate for your chosen hostname and terminate HTTPS at the proxy.

  2. Authentication — enforce whatever you need (basic auth as shown above, OAuth/OIDC, mTLS, an IP allowlist) before a request is forwarded.

  3. Forwarding — proxy the authenticated request to hal0-api on 127.0.0.1:8080 (or its LAN address), and bind hal0 itself so only the proxy can reach it.

hal0 doesn’t manage user accounts or TLS, but it isn’t a bare-open socket either. A few narrower gates exist inside hal0 itself:

  • MCP host and origin allowlist — the admin (/mcp/admin) and memory (/mcp/memory) MCP mounts default to a localhost-only DNS-rebinding allowlist (127.0.0.1, localhost, [::1]); a non-localhost Host header gets a bare 421 Invalid Host header. Widen it with HAL0_MCP_ALLOWED_HOSTS (and, if needed, HAL0_MCP_ALLOWED_ORIGINS) to reach /mcp/* from another machine or through your proxy. This is a host/origin gate against DNS rebinding, not authentication — see Security.
  • Agent-chat origin allowlist + session cookie — the agent-chat WebSocket routes check the browser Origin against HAL0_ALLOWED_ORIGINS (default covers http://hal0.local, http://localhost:5173, and http://127.0.0.1:8080) on every upgrade, and require an HMAC-SHA256 session cookie (hal0_session, HttpOnly, SameSite=Lax, ~8h TTL) that is minted by GET /api/agents/{id}/session/handshake and verified on both the WS upgrade and the REST session endpoints. The signing secret is generated on first use at /var/lib/hal0/agents/secret.bin (mode 0600) and never leaves the hal0 service user. This stops a drive-by site from WebSocket-ing into the chat proxy using a browser session that’s already open to hal0 — it is not a login system.
  • X-hal0-Agent identity header — every agent/MCP caller identifies itself with the X-hal0-Agent header (validated to a bounded [a-zA-Z0-9_-]{1,64} id), which stamps the audit log and resolves the private:<agent> memory namespace consistently whether the call arrived over REST or MCP. There is no Bearer/API-key surface — this header is an identity claim, not a secret, so it only means something once you’ve already gated access at the network layer (LAN-only or your proxy).
  • Gated agent actions — privileged or destructive agent/MCP actions don’t execute immediately; they enqueue onto an approval queue that a human clears from the dashboard, the CLI, or the approvals API. See Agents for how personas, tool gating, and the approval queue fit together.

None of the above is a substitute for authentication at the edge — they narrow specific browser/DNS-rebinding attack surfaces and give you an audit trail, but they don’t gate who can reach /api/* or /v1/* in the first place. That’s still the reverse proxy’s job.

  • Security — the full security posture and threat model this page is a reference companion to.
  • Connect an MCP client — configuring HAL0_MCP_ALLOWED_HOSTS/HAL0_MCP_ALLOWED_ORIGINS and the X-hal0-Agent header in practice.
  • Environment variables — the full env-var reference, including HAL0_ALLOWED_ORIGINS and the MCP allowlist knobs.