fix(daemon): secure HTTP API by default#62
Conversation
Authenticate all HTTP routes, allowlist CORS, bind to 127.0.0.1, CSRF-protect the dashboard, and enforce session ownership so principals cannot enumerate or read each other's sessions. BREAKING CHANGE: unauthenticated HTTP clients and 0.0.0.0 bind no longer work without explicit opt-in (token / --host / ABBENAY_HTTP_AUTH).
Contributor review (maintainer assist)Verdict: needs work before merge — code/security defaults look strong and CI/local gates are green, but there are merge-blocking doc/coordination items and one fail-closed gap vs the stated DR. Checklist
What looks solid
Blockers / must coordinate before merge
Should-fix (security posture vs DR text)
Non-blocking follow-ups
Optional nits (not blocking)
No code push from this review. Happy to help renumber DRs / patch the K8s probe YAML on the branch if wanted. |
Resolve conflicts with gRPC TLS (#63): keep HTTP --host and auth, require --grpc-tls in the container CMD, and renumber HTTP DRs to 030/031.
Refuse auth-disabled binds beyond loopback, add Authorization to K8s probe examples, prefer POST /login over query tokens, set Secure cookies behind TLS proxies, and timing-safe-compare login credentials.
Thanks @cidrblock for the thorough review: Blockers DR-029 collision (#70) — Resolved by merging main (after #63). gRPC TLS stays DR-029; this PR is DR-030 (HTTP auth) / DR-031 (session ownership). Optional nits Prefer GET/POST /login over /?token= (query login kept for compat, timing-safe compare) Still open (non-blocking) #71 unrecognized gRPC consumer token → don’t map to local — tracking as a follow-up unless you want it in this PR. |
Re-review after your fixesVerified on the current branch tip:
CI green, up to date with main. Updated verdict: merge-ready (with #71 tracked separately). |
Summary
Secure the daemon HTTP surface by default: require Bearer (or SameSite cookie) auth on
/api/*,/v1/*, and/mcp; restrict CORS to an explicit origin allowlist; bind HTTP to127.0.0.1unless explicitly opted out; add dashboard CSRF; and isolate sessions by owner principal across HTTP and gRPC.BREAKING CHANGE: Unauthenticated HTTP clients and
0.0.0.0bind no longer work without explicit opt-in (ABBENAY_API_TOKEN/ token file,--host/ABBENAY_HTTP_HOST/server.host, orABBENAY_HTTP_AUTH=0for local-dev only on loopback).Jira: AAP-82788
Changes
http-security.ts: token resolution (env → config → persistedhttp-api-token→ generate), CORS allowlist middleware, Bearer/cookie auth + CSRF, localhost bind helperscreateWebApp/startEmbeddedWebServer; default bind127.0.0.1; refuse to start when auth is disabled on a non-loopback bind (fail-closed, matches gRPC TLS posture)SameSite=StrictAPI cookie + CSRF injection;apiFetchfor authenticated requests;?token=login redirectlocal/http:<fingerprint>/consumer:<name>) with owner-scoped list/get/delete/chat on HTTP and gRPCserver.api_token/api_token_env/host/cors_origins; docs (CONFIGURATION, CONTAINER, GETTING_STARTED, README, DR-030/031)--host 0.0.0.0+ Bearer healthcheck; K8s sample probes includeAuthorizationheaders; documentABBENAY_API_TOKENTest plan
lint-and-test, build matrix, container, package-python)npm run lint+npm teston PR branchAuthorization: Bearer …(docs(container): add Authorization headers to Kubernetes HTTP probes #69)ABBENAY_HTTP_AUTH=0+ non-loopback bind (fix(daemon): refuse to start when HTTP auth disabled on non-loopback bind #68)127.0.0.1; unauthenticated/api/health→ 401; Bearer works--host 0.0.0.0with token; foreignOriginrejectedABBENAY_API_TOKEN— healthcheck and curl with Authorization succeedABBENAY_HTTP_AUTH=0 --host 0.0.0.0refuses to start