Skip to content

fix(daemon): secure HTTP API by default#62

Merged
sudhirverma merged 3 commits into
mainfrom
fix/secure-http-api-defaults
Jul 16, 2026
Merged

fix(daemon): secure HTTP API by default#62
sudhirverma merged 3 commits into
mainfrom
fix/secure-http-api-defaults

Conversation

@sudhirverma

@sudhirverma sudhirverma commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

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 to 127.0.0.1 unless 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.0 bind no longer work without explicit opt-in (ABBENAY_API_TOKEN / token file, --host / ABBENAY_HTTP_HOST / server.host, or ABBENAY_HTTP_AUTH=0 for local-dev only on loopback).

Jira: AAP-82788

Changes

  • Add http-security.ts: token resolution (env → config → persisted http-api-token → generate), CORS allowlist middleware, Bearer/cookie auth + CSRF, localhost bind helpers
  • Wire auth into createWebApp / startEmbeddedWebServer; default bind 127.0.0.1; refuse to start when auth is disabled on a non-loopback bind (fail-closed, matches gRPC TLS posture)
  • Dashboard: HttpOnly SameSite=Strict API cookie + CSRF injection; apiFetch for authenticated requests; ?token= login redirect
  • Session ownership (local / http:<fingerprint> / consumer:<name>) with owner-scoped list/get/delete/chat on HTTP and gRPC
  • Config: server.api_token / api_token_env / host / cors_origins; docs (CONFIGURATION, CONTAINER, GETTING_STARTED, README, DR-030/031)
  • Container: --host 0.0.0.0 + Bearer healthcheck; K8s sample probes include Authorization headers; document ABBENAY_API_TOKEN
  • Tests: unit + integration coverage for auth, CORS, bind default, session isolation, auth-disable escape hatch, and refuse-to-start when auth disabled beyond loopback

Test plan

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).
@cidrblock

Copy link
Copy Markdown
Collaborator

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

  • Up to date with main (0 behind / 1 ahead)
  • CI green (lint-and-test, build matrix, container, package-python)
  • Local npm run lint (0 errors) + npm test (499 daemon + 24 vscode) in worktree
  • PR description updated to Summary / Changes / Test plan
  • DR numbers coordinated with PR fix(daemon): require TLS or --insecure for non-loopback gRPC (C2) #63 (both claim DR-029)
  • K8s probe example includes Authorization (or tracked fix landed)

What looks solid

  • Closes the real pre-PR gap: unauthenticated HTTP + Access-Control-Allow-Origin: * + bind-all-interfaces
  • Token resolution chain, timing-safe compare, CORS allowlist (never *), cookie SameSite=Strict + CSRF for cookie-only mutating requests
  • Loopback auto cookie establish is scoped; remote needs ?token= (or Bearer)
  • Session ownership enforced on HTTP session routes and gRPC session RPCs; cross-owner → not found
  • Container/docs/README updated for Bearer + --host 0.0.0.0; integration tests cover auth/CORS/isolation/escape hatch
  • Dashboard mostly switched to apiFetch (one leftover fetch(modelsUrl) on GET is harmless)

Blockers / must coordinate before merge

  1. DR-029 collision with PR fix(daemon): require TLS or --insecure for non-loopback gRPC (C2) #63fix(daemon): require TLS or --insecure for non-loopback gRPC (C2) #63 also adds DR-029 (gRPC TLS). Renumber this PR’s decisions (e.g. HTTP → DR-030, ownership → DR-031 if fix(daemon): require TLS or --insecure for non-loopback gRPC (C2) #63 lands first) and update cross-refs. Tracked: docs(decisions): resolve DR-029 number collision between HTTP auth and gRPC TLS PRs #70
  2. K8s sample probes will 401docs/CONTAINER.md Deployment still has bare httpGet on /api/health while prose says to send Authorization. Fix in this PR or land docs(container): add Authorization headers to Kubernetes HTTP probes #69 with it.

Should-fix (security posture vs DR text)

  1. ABBENAY_HTTP_AUTH=0 + non-loopback only warns — DR-029 says must not combine; gRPC TLS (fix(daemon): require TLS or --insecure for non-loopback gRPC (C2) #63) fails closed. Prefer refuse-to-start. Tracked: fix(daemon): refuse to start when HTTP auth disabled on non-loopback bind #68

Non-blocking follow-ups

Topic Issue
Fail-closed when auth disabled beyond loopback #68
K8s probe Authorization headers #69
Resolve DR-029 numbering vs #63 #70
Unrecognized gRPC consumer token → don’t map to local #71

Optional nits (not blocking)

  • ?token= login leaks via history/Referer/logs — acceptable for now; POST login later if needed
  • Cookie Secure flag when HTTP TLS exists
  • Query-string token compare is not timing-safe (minor vs Bearer path)
  • Auto-generated token inside containers is hard to retrieve — docs already push setting ABBENAY_API_TOKEN
  • Legacy sessions without owner become CLI-only (local); worth a one-line migration note in GETTING_STARTED

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.
@sudhirverma

sudhirverma commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

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

  • Up to date with main (0 behind / 1 ahead)
  • CI green (lint-and-test, build matrix, container, package-python)
  • Local npm run lint (0 errors) + npm test (499 daemon + 24 vscode) in worktree
  • PR description updated to Summary / Changes / Test plan
  • DR numbers coordinated with PR fix(daemon): require TLS or --insecure for non-loopback gRPC (C2) #63 (both claim DR-029)
  • K8s probe example includes Authorization (or tracked fix landed)

What looks solid

  • Closes the real pre-PR gap: unauthenticated HTTP + Access-Control-Allow-Origin: * + bind-all-interfaces
  • Token resolution chain, timing-safe compare, CORS allowlist (never *), cookie SameSite=Strict + CSRF for cookie-only mutating requests
  • Loopback auto cookie establish is scoped; remote needs ?token= (or Bearer)
  • Session ownership enforced on HTTP session routes and gRPC session RPCs; cross-owner → not found
  • Container/docs/README updated for Bearer + --host 0.0.0.0; integration tests cover auth/CORS/isolation/escape hatch
  • Dashboard mostly switched to apiFetch (one leftover fetch(modelsUrl) on GET is harmless)

Blockers / must coordinate before merge

  1. DR-029 collision with PR fix(daemon): require TLS or --insecure for non-loopback gRPC (C2) #63fix(daemon): require TLS or --insecure for non-loopback gRPC (C2) #63 also adds DR-029 (gRPC TLS). Renumber this PR’s decisions (e.g. HTTP → DR-030, ownership → DR-031 if fix(daemon): require TLS or --insecure for non-loopback gRPC (C2) #63 lands first) and update cross-refs. Tracked: docs(decisions): resolve DR-029 number collision between HTTP auth and gRPC TLS PRs #70
  2. K8s sample probes will 401docs/CONTAINER.md Deployment still has bare httpGet on /api/health while prose says to send Authorization. Fix in this PR or land docs(container): add Authorization headers to Kubernetes HTTP probes #69 with it.

Should-fix (security posture vs DR text)

  1. ABBENAY_HTTP_AUTH=0 + non-loopback only warns — DR-029 says must not combine; gRPC TLS (fix(daemon): require TLS or --insecure for non-loopback gRPC (C2) #63) fails closed. Prefer refuse-to-start. Tracked: fix(daemon): refuse to start when HTTP auth disabled on non-loopback bind #68

Non-blocking follow-ups

Topic Issue
Fail-closed when auth disabled beyond loopback #68
K8s probe Authorization headers #69
Resolve DR-029 numbering vs #63 #70
Unrecognized gRPC consumer token → don’t map to local #71

Optional nits (not blocking)

  • ?token= login leaks via history/Referer/logs — acceptable for now; POST login later if needed
  • Cookie Secure flag when HTTP TLS exists
  • Query-string token compare is not timing-safe (minor vs Bearer path)
  • Auto-generated token inside containers is hard to retrieve — docs already push setting ABBENAY_API_TOKEN
  • Legacy sessions without owner become CLI-only (local); worth a one-line migration note in GETTING_STARTED

No code push from this review. Happy to help renumber DRs / patch the K8s probe YAML on the branch if wanted.

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).
K8s probes (#69) — Sample Deployment now includes ABBENAY_API_TOKEN in the Secret and Authorization httpHeaders on liveness/readiness probes.
Should-fix 3. Fail-closed (#68) — ABBENAY_HTTP_AUTH=0 + non-loopback bind now refuses to start (assertHttpAuthBindAllowed), matching the gRPC TLS posture.

Optional nits

Prefer GET/POST /login over /?token= (query login kept for compat, timing-safe compare)
Cookie Secure when HTTPS / X-Forwarded-Proto: https
Startup note to set ABBENAY_API_TOKEN when the token was auto-generated
Legacy sessions without owner → CLI/local only note in GETTING_STARTED

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.

@cidrblock

Copy link
Copy Markdown
Collaborator

Re-review after your fixes

Verified on the current branch tip:

Item Status
#70 DR numbering (HTTP DR-030 / ownership DR-031; gRPC TLS remains DR-029) Fixed — closed
#69 K8s probe Authorization + Containerfile HEALTHCHECK Fixed — closed
#68 fail-closed assertHttpAuthBindAllowed + tests Fixed — closed
Optional nits (POST /login, Cookie Secure, GETTING_STARTED owner note) Present
#71 unrecognized gRPC consumer token ownership Still deferred (OK as follow-up)

CI green, up to date with main. Updated verdict: merge-ready (with #71 tracked separately).

@sudhirverma
sudhirverma merged commit fc025b1 into main Jul 16, 2026
6 checks passed
@sudhirverma
sudhirverma deleted the fix/secure-http-api-defaults branch July 16, 2026 16:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants