[M3] Dev-test stack is served over plaintext HTTP to the LAN — put TLS on the transports (secret-unmask debug hook stays) #78

Open
opened 2026-09-20 18:16:46 +00:00 by grokbot_ty · 1 comment
Owner

Summary

The dev-test stack has no TLS on any transport: deploy/Caddyfile.fragment is the intended TLS edge but was never merged into a running Caddy, so every request — including the API — travels as plaintext HTTP over the LAN. Given that this environment intentionally carries a debug hook that can return stored secrets in plaintext, the missing TLS is the actual defect.

Scope change (2026-09-20, requested by owner)

  • Re-scoped from "stop returning secrets in /api/v1/settings" to "put SSL/TLS on the transports".
  • SLAB_UAT_UNMASK_SECRETS=1 (deploy/.env) is an intentional dev/test debugging hook — it is needed to inspect stored secrets on this box and it stays.
  • Removed from this issue: deleting or gating the hook, and narrowing CORS off allow_origins=["*"] (backend/app.py:135-141, raised separately if wanted).
  • What remains is the transport: no plaintext-HTTP path to the app or its API.

Observed evidence

deploy/INVENTORY.md:

Port block 10880–10889 on ubudual5060 is allocated to this project.
The dev-test environment binds 0.0.0.0 so any machine on 192.168.1.0/24 can reach it.
URLs: http://192.168.1.202:10881 (LAN) · http://100.64.0.9:10881 (Tailscale)

deploy/override-m3.yml binds the frontend to 0.0.0.0:10881; the frontend container's nginx (deploy/nginx-m1.conf) proxies /api/ to FastAPI on the compose network.

deploy/Caddyfile.fragment is written as the TLS edge but is not in play:

{$APP_HOSTNAME} {
	tls internal
	handle /api/* {
		reverse_proxy fastapi:8000
	}
	handle {
		reverse_proxy frontend:80
	}
}

Checked on ubudual5060: no caddy process is running and the caddy service is inactive, so nothing terminates TLS for this stack. (The fragment's comment says "Merge into host Caddy" — that merge has not happened.)

Consequence, probed read-only against the live container over that plaintext hop (values not printed):

$ curl -s http://127.0.0.1:10881/api/v1/settings     # LAN-reachable on 0.0.0.0:10881
  woo_app_password     PLAINTEXT STRING len=29       # only because the debug hook is on
  inference_api_key    PLAINTEXT STRING len=5
  inference_base_url   PLAINTEXT STRING len=28

Why this is the real issue

The hook is fine in dev/test. What turns a debugging convenience into a LAN-visible credential read is that the bytes are unencrypted and unauthenticated on the wire, on a shared 192.168.1.0/24. That applies to any future debug surface too, not just this one route. The Tailscale address (100.64.0.9) is already an encrypted transport; the LAN HTTP path is not.

Acceptance

  • The dev-test stack serves over TLS on its transports: either the Caddy edge actually running with the fragment merged (tls internal, /api/* handled before the frontend catch-all), or the 0.0.0.0 LAN bind dropped in favour of the encrypted Tailscale path only.
  • No plaintext-HTTP URL serves the app or its API — curl -i http://192.168.1.202:10881/api/v1/settings must refuse or redirect to https://, never return 200 with the settings payload.
  • The unmask hook stays and stays documented at its definition as dev/test-only, with the existing "Remove before prod" invariant intact (backend/settings.py:62-65), and default-off behaviour unchanged (SETTINGS-75 masking when unset).
  • Re-verify by fetching https://<host>/api/v1/settings with the hook off and confirming {"configured": true} shapes rather than strings.

Related

  • Follow-up review finding F1 (transport portion) from the M3 code review
  • Requirements: SETTINGS-75, SETTINGS-85, APP-30, APP-55, OPS-25
  • Config: deploy/INVENTORY.md, deploy/Caddyfile.fragment, deploy/override-m3.yml, deploy/docker-compose.yml
  • Commits: 34ee7ef (temporary UAT secret-unmask gate), d614123 (UAT secrets as plain text inputs)
## Summary The dev-test stack has no TLS on any transport: `deploy/Caddyfile.fragment` is the intended TLS edge but was never merged into a running Caddy, so every request — including the API — travels as plaintext HTTP over the LAN. Given that this environment intentionally carries a debug hook that can return stored secrets in plaintext, the missing TLS is the actual defect. ## Scope change (2026-09-20, requested by owner) - **Re-scoped** from "stop returning secrets in `/api/v1/settings`" to "put SSL/TLS on the transports". - `SLAB_UAT_UNMASK_SECRETS=1` (`deploy/.env`) is an **intentional dev/test debugging hook** — it is needed to inspect stored secrets on this box and it stays. - Removed from this issue: deleting or gating the hook, and narrowing CORS off `allow_origins=["*"]` (`backend/app.py:135-141`, raised separately if wanted). - What remains is the transport: no plaintext-HTTP path to the app or its API. ## Observed evidence `deploy/INVENTORY.md`: > Port block `10880`–`10889` on ubudual5060 is allocated to this project. > The dev-test environment binds `0.0.0.0` so any machine on `192.168.1.0/24` can reach it. > URLs: `http://192.168.1.202:10881` (LAN) · `http://100.64.0.9:10881` (Tailscale) `deploy/override-m3.yml` binds the frontend to `0.0.0.0:10881`; the frontend container's nginx (`deploy/nginx-m1.conf`) proxies `/api/` to FastAPI on the compose network. `deploy/Caddyfile.fragment` is written as the TLS edge but is not in play: ``` {$APP_HOSTNAME} { tls internal handle /api/* { reverse_proxy fastapi:8000 } handle { reverse_proxy frontend:80 } } ``` Checked on `ubudual5060`: no `caddy` process is running and the `caddy` service is inactive, so nothing terminates TLS for this stack. (The fragment's comment says "Merge into host Caddy" — that merge has not happened.) Consequence, probed read-only against the live container over that plaintext hop (values not printed): ``` $ curl -s http://127.0.0.1:10881/api/v1/settings # LAN-reachable on 0.0.0.0:10881 woo_app_password PLAINTEXT STRING len=29 # only because the debug hook is on inference_api_key PLAINTEXT STRING len=5 inference_base_url PLAINTEXT STRING len=28 ``` ## Why this is the real issue The hook is fine in dev/test. What turns a debugging convenience into a LAN-visible credential read is that the bytes are unencrypted and unauthenticated on the wire, on a shared `192.168.1.0/24`. That applies to any future debug surface too, not just this one route. The Tailscale address (`100.64.0.9`) is already an encrypted transport; the LAN HTTP path is not. ## Acceptance - The dev-test stack serves over TLS on its transports: either the Caddy edge actually running with the fragment merged (`tls internal`, `/api/*` handled before the frontend catch-all), or the `0.0.0.0` LAN bind dropped in favour of the encrypted Tailscale path only. - No plaintext-HTTP URL serves the app or its API — `curl -i http://192.168.1.202:10881/api/v1/settings` must refuse or redirect to `https://`, never return 200 with the settings payload. - The unmask hook stays and stays documented at its definition as dev/test-only, with the existing "Remove before prod" invariant intact (`backend/settings.py:62-65`), and default-off behaviour unchanged (`SETTINGS-75` masking when unset). - Re-verify by fetching `https://<host>/api/v1/settings` with the hook off and confirming `{"configured": true}` shapes rather than strings. ## Related - Follow-up review finding F1 (transport portion) from the M3 code review - Requirements: SETTINGS-75, SETTINGS-85, APP-30, APP-55, OPS-25 - Config: `deploy/INVENTORY.md`, `deploy/Caddyfile.fragment`, `deploy/override-m3.yml`, `deploy/docker-compose.yml` - Commits: `34ee7ef` (temporary UAT secret-unmask gate), `d614123` (UAT secrets as plain text inputs)
grokbot_ty added the blocking label 2026-09-20 18:16:46 +00:00
grokbot_ty changed title from [M3] UAT deploy serves the Woo app password and inference key in plaintext over the LAN (SLAB_UAT_UNMASK_SECRETS=1) to [M3] Dev-test stack is served over plaintext HTTP to the LAN — put TLS on the transports (secret-unmask debug hook stays) 2026-09-20 18:19:02 +00:00
grokbot_ty removed the blocking label 2026-09-20 18:19:43 +00:00
Author
Owner

Caddyfile.fragment exists with tls internal, not yet merged into running Caddy. Moving to post-M3.

Caddyfile.fragment exists with tls internal, not yet merged into running Caddy. Moving to post-M3.
grokbot_ty added the post-launch label 2026-09-24 18:56:20 +00:00
Sign in to join this conversation.
No Label post-launch
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Ty_Tech/SlabUploader#78