fix(deploy): terminate TLS in front of services via Caddy reverse proxy (#747)#827
Conversation
…xy (#747) App processes now bind loopback (127.0.0.1); a TLS-terminating Caddy reverse proxy is the sole public listener. JWT bearer tokens and API keys no longer travel in cleartext on shared-VPS interfaces. - ecosystem.{staging,production}.config.js: frontend binds 127.0.0.1, not 0.0.0.0 - .env.{staging,production}.example: HOST=127.0.0.1; public origins are https/wss (same-origin behind the proxy); add .env.production.example - deploy/Caddyfile.example: auto-TLS (Let's Encrypt), path-routes /api,/auth,/ws to the backend and everything else to Next.js; transparent WS upgrades - scripts/remote-setup.sh: install Caddy; firewall exposes only 80/443 and denies the loopback-only app ports (14100/14200) - deploy/README.md: TLS reverse-proxy setup + routing - tests/test_deploy_config.py: assert loopback binding, https/wss origins, Caddyfile routing, and proxy provisioning
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 11 minutes Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Review: fix(deploy): terminate TLS via Caddy reverse proxy (#747)Solid piece of work overall — the loopback-binding + Caddy design is the right shape (same-origin, no CORS, auto-TLS renewal), the docs are clear, and the new tests actually assert the security-relevant properties (loopback binding, https/wss origins, Caddyfile routing, firewall denial of the app ports) rather than just checking file existence. No prior reviews exist on this PR yet (only a rate-limited CodeRabbit placeholder with no content), so this is the first substantive pass. Critical: the automated deploy pipeline still writes
|
…proxy rate limiting (#747) Third-party review (opencode/GLM) findings: - Major: backend was started without --host, so loopback binding depended on an operator-maintained HOST env var — a stale HOST=0.0.0.0 in an existing deploy would leave the backend exposed. Pin --host explicitly in both ecosystems (staging literal 127.0.0.1; production HOST const defaulting to 127.0.0.1). - Major: behind TLS termination the app sees client 127.0.0.1. The app has its own trusted-proxy mechanism (RATE_LIMIT_TRUSTED_PROXIES), so document RATE_LIMIT_TRUSTED_PROXIES=127.0.0.1 in the env examples rather than enabling uvicorn proxy_headers (which would bypass the app's spoof protection). - Minor: note that 'tls internal' needs the local CA trusted (caddy trust). - Minor: clarify CORS is same-origin but keep CORS_ALLOWED_ORIGINS set. - Close test gap: assert the ecosystem backend binds loopback, not just .env.
Third-party review (opencode / GLM) + triageRan an independent review against the actual codebase. It verified routing is correct (frontend calls
Demo — TLS proxy end-to-end (
|
| Request | Result |
|---|---|
https://localhost/ (TLS on :443) |
200, scheme HTTPS |
/ → frontend 127.0.0.1:14100 |
✓ routed |
/api/health, /auth/x → backend 127.0.0.1:14200 |
✓ routed |
http://localhost/ |
308 → https (auto-redirect) |
Both acceptance criteria demonstrated with outcome evidence.
Follow-up review (after 5d8ee70)Checked this against my prior review and the opencode/GLM triage in the thread above, so I won't re-litigate what's already resolved:
New finding — the CI/CD redeploy path won't actually pick up the new
|
Follow-up review (after 53b3529)Only change since my last pass is Spot-checked one thing I hadn't verified yet: Still open from my prior review (unaddressed, not a regression from this PR but in code it actively edits)
Everything else already covered across the thread stands: loopback binding, the |
Closes #747
Problem
Frontend (
next start -H 0.0.0.0) and backend (HOST=0.0.0.0) bound allinterfaces over plaintext
http:///ws://, andremote-setup.shinstalled noreverse proxy. On the shared VPS, JWT bearer tokens and API keys travelled in
cleartext on every interface.
Change
App processes now bind loopback only; a TLS-terminating Caddy reverse
proxy is the sole public listener. Because the frontend already supports
same-origin operation (
NEXT_PUBLIC_API_URL || ''), Caddy path-routes a singlehttps://origin — no CORS, no plaintext leaving the host.127.0.0.1:14200127.0.0.1:14100:80,:443ecosystem.{staging,production}.config.js— frontend binds127.0.0.1.env.{staging,production}.example—HOST=127.0.0.1; public origins arehttps:///wss://(new.env.production.example)deploy/Caddyfile.example— automatic Let's Encrypt TLS; routes/api,/auth,/ws,/health,/docs→ backend, everything else → Next.js;WebSocket upgrades proxied transparently
scripts/remote-setup.sh— installs Caddy; firewall exposes only80/443anddenies the loopback-only app ports
deploy/README.md— setup + routingThe existing CSP (
security-headers.js) already derivesconnect-srcfrom theNEXT_PUBLIC_*env at build time, so it picks up thehttps/wssorigins withno change.
Acceptance criteria
127.0.0.1.https:///wss://.Testing
tests/test_deploy_config.pyextended: loopback binding (both ecosystems +env HOST),
https/wssorigins, Caddyfile routing to both loopback ports,and proxy provisioning in
remote-setup.sh.caddy validateruns when caddyis installed (skipped otherwise).
deploy/Caddyfile.examplewithcaddy validate(Caddy 2 container):Valid configuration, auto HTTP→HTTPS redirect + TLS policy enabled.
Known limitations
server.py's CLI--hostdefault stays0.0.0.0(local dev /codeframe serveconvenience); the deploy env pins
HOST=127.0.0.1. Production binding isenforced by config, not the code default.
Caddyfile.exampleuses the staging ports (14100/14200); production defaults(3000/8000) are noted in a comment to adjust.