Skip to content

Security model

octo is a single-user, personal agent. Its server (octo serve) executes arbitrary shell commands on behalf of its one trusted operator — that is the product, not a vulnerability. This page states where the security boundary sits, what protects it, and what’s deliberately out of scope.

  • One user. No accounts or roles. Whoever holds the access key (or sits at the machine) has full control, including command execution via chat.
  • Loopback is trusted. Requests from 127.0.0.1/::1 are exempt from key authentication — anything that can already run code as a local user is outside the boundary anyway.
  • Everything else needs the key. octo serve binds to 127.0.0.1:8088 by default; binding wider (-addr :8088) makes every API and WebSocket request from a non-loopback client require the access key.
Threat Defense
LAN / internet attacker calling an exposed API Loopback-only default bind; 256-bit access key (constant-time compare) on every non-loopback request
Malicious website CSRF-ing http://localhost:8088 from your browser Origin must be local or --cors-allowlisted (a literal * is never honored); auth cookie is SameSite=Strict
DNS rebinding (attacker domain resolving to 127.0.0.1) The loopback exemption requires a local Host header
Spoofed client IPs X-Forwarded-For is never consulted for the loopback exemption
XSSI reads of uploaded files X-Content-Type-Options: nosniff on served uploads

IM channels (Feishu, DingTalk, Discord, …) authenticate separately via each platform’s own bot credentials plus octo’s chat/user binding; adapters hold outbound connections only and expose no inbound HTTP routes.

  • Hostile local processes. Loopback traffic is trusted; malware running as any local user on the same machine can reach the API. A shared or compromised machine is outside what the access key can help with.
  • Plaintext transport. The key travels in cookies/headers over HTTP. On untrusted networks, terminate TLS in front (a reverse proxy, tailscale serve) or tunnel.
  • Brute force. No lockout or rate limiting — the key is 256 bits of crypto/rand, so online guessing isn’t viable regardless.

Resolution order: octo serve --access-key flag → OCTO_ACCESS_KEY env var → access_key in ~/.octo/config.yml → auto-generated on first start and persisted (mode 0600). When the bind address isn’t loopback-only, startup prints a ready-to-open URL with the key embedded; the Web UI stores it and strips it from the address bar.

Clients present it as Authorization: Bearer <key>, X-Access-Key, or the octo_access_key cookie (the access_key query parameter works on /ws only). GET /api/health and GET /api/version are the only unauthenticated routes, and carry no secrets.

To rotate: edit access_key in config.yml and restart (or POST /api/restart).

octo upgrade (and the Web UI’s upgrade button) verifies the downloaded archive’s SHA-256 against the release’s checksums.txt, both fetched over GitHub’s TLS. That catches transport corruption and mirror tampering — not a compromised GitHub account publishing a malicious release; there’s no signature layer yet. The version check behind the web badge is automatic; the install never is, and sits behind access-key auth like every other mutating endpoint.

Open a GitHub security advisory or email the maintainer privately — please don’t file public issues for exploitable bugs.