Skip to content

Authentication & Security

hal0-api binds on :8080 with no built-in network gate. That is the correct default for the common case: an LXC behind your homelab router, reachable only over the LAN or through a reverse proxy you already operate and trust.

If hal0 will be reachable from anything outside that trust boundary — a port-forward, a cloud VM, a shared Tailscale network, a public hostname — put a reverse proxy in front of it. hal0 has no built-in account system and does not enforce credentials at the API layer; the proxy is where authentication and TLS termination live.

Three patterns cover the common deployments.

If you already have Traefik routing *.thinmint.dev (or similar), point a router at http://<hal0-host>:8080. Traefik handles TLS and whatever middleware you use (basic auth, Forward Auth, Authelia, etc.).

# traefik dynamic config snippet
http:
routers:
hal0:
rule: "Host(`hal0.thinmint.dev`)"
entryPoints: [websecure]
tls:
certResolver: le
middlewares: [my-auth]
service: hal0
services:
hal0:
loadBalancer:
servers:
- url: "http://10.0.1.142:8080"

hal0-api trusts the LAN. No special header wiring is required; the proxy is the authentication boundary.

server {
listen 443 ssl;
server_name hal0.example.com;
ssl_certificate /etc/letsencrypt/live/hal0.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hal0.example.com/privkey.pem;
# Put your auth directive here — basic_auth, auth_request, etc.
auth_basic "hal0";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://10.0.1.142:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
}
}

The Upgrade / Connection headers are required for the dashboard’s WebSocket connections to pass through.

For internet-facing deployments without opening inbound firewall ports:

~/.cloudflared/config.yml
tunnel: <tunnel-id>
credentials-file: /root/.cloudflared/<tunnel-id>.json
ingress:
- hostname: hal0.example.com
service: http://localhost:8080
- service: http_status:404

Add a Cloudflare Access policy on hal0.example.com to gate entry with email OTP, GitHub SSO, or any other identity provider. The tunnel process on the hal0 host dials outbound; no inbound port is opened.

Built-in gate: chat-proxy Origin allowlist

Section titled “Built-in gate: chat-proxy Origin allowlist”

The one in-process network control hal0-api enforces is an HTTP Origin allowlist on the chat-proxy WebSocket endpoint. This prevents a page on a foreign origin from opening a WebSocket to the local API (DNS-rebinding / cross-site WebSocket hijacking).

Knob: HAL0_ALLOWED_ORIGINS in /etc/hal0/api.env

Terminal window
# Default — only the local dashboard origin
HAL0_ALLOWED_ORIGINS=http://localhost:8080
# Multiple origins (comma-separated)
HAL0_ALLOWED_ORIGINS=https://hal0.thinmint.dev,http://localhost:8080

The API also issues an HMAC-signed HttpOnly / SameSite=Strict session cookie for browser sessions initiated from an allowed origin. This cookie:

  • Is signed with a server-side secret (HAL0_SESSION_SECRET in /etc/hal0/api.env); tampering invalidates it.
  • Is HttpOnly — not readable by JavaScript.
  • Is SameSite=Strict — not sent on cross-site requests.
  • Does not replace proxy-level auth. It scopes the browser session after the proxy has already admitted the request.

The MCP endpoint has its own DNS-rebinding allowlists, separate from HAL0_ALLOWED_ORIGINS.

VariablePurpose
HAL0_MCP_ALLOWED_HOSTSComma-separated Host header values the MCP server will accept. Prevents DNS-rebinding attacks where a browser navigates to a hostile name that resolves to 127.0.0.1.
HAL0_MCP_ALLOWED_ORIGINSComma-separated Origin header values for the MCP WebSocket upgrade.
/etc/hal0/api.env
HAL0_MCP_ALLOWED_HOSTS=hal0.thinmint.dev,localhost
HAL0_MCP_ALLOWED_ORIGINS=https://hal0.thinmint.dev,http://localhost:8080

An MCP request whose Host is not in the allowlist gets 421 Invalid Host. Missing or empty HAL0_MCP_ALLOWED_HOSTS defaults to localhost,127.0.0.1.

VariableDefaultEffect
HAL0_ALLOWED_ORIGINShttp://localhost:8080WebSocket origin allowlist for the chat proxy.
HAL0_SESSION_SECRETauto-generated at installHMAC key for the HttpOnly session cookie.
HAL0_MCP_ALLOWED_HOSTSlocalhost,127.0.0.1Host header allowlist for the MCP endpoint.
HAL0_MCP_ALLOWED_ORIGINShttp://localhost:8080Origin allowlist for the MCP WebSocket upgrade.

All four live in /etc/hal0/api.env. Edit the file and restart hal0-api for changes to take effect:

Terminal window
sudo systemctl restart hal0-api