Harden internet deployment: fail-closed auth behind proxies, SSRF and debug limits #14

Merged
kubus merged 1 commit from security/internet-deploy-hardening into main 2026-09-30 15:11:42 +00:00
Owner

Summary

  • Fail-closed auth: Scout now refuses to start without credentials when listening beyond loopback, configured for a reverse proxy (SCOUT_TRUST_PROXY or SCOUT_PUBLIC_ORIGIN set), or when SCOUT_AUTH=on is explicit. This prevents unintended internet exposure of an unauthenticated instance.
  • Proxied-request rejection: Without credentials, Scout rejects any API request carrying X-Forwarded-For, Forwarded, X-Real-IP, or CF-Connecting-IP headers, so a proxy added later cannot publish it unprotected.
  • Debug API time limits: Queries now run in a child process with a 5-second timeout and 32 MB output limit per query, preventing runaway SQL from stalling the event loop or scheduler.
  • Secret redaction: Embedded encrypted secrets (e.g. within string concatenation in debug SQL) are now replaced with [redacted: encrypted secret].
  • Browser SSRF protection: Rendered marketplace pages cannot request loopback, private, or single-label hostnames (blocking browser-based SSRF to internal services) or open WebSockets.
  • Browserless token: Compose now requires SCOUT_BROWSER_TOKEN (Browserless v2), preventing scripts on rendered pages from hijacking the browser.
  • Hostname validation: New SCOUT_ALLOWED_HOSTS accepts extra hostnames while auth is off (e.g. scout.lan over DNS rebinding); IP addresses and localhost are always accepted.
  • Updated docs and examples: Clearer guidance on authentication, reverse proxy setup, and requirements for Docker Compose and systemd deployments.

Testing

  • Local loopback mode: Verify npm run build followed by SCOUT_AUTH=off npm start works without credentials on 127.0.0.1.
  • Internet deployment: Confirm Scout refuses to start when SCOUT_HOST=0.0.0.0 (or publicly bound) without SCOUT_PASSWORD_HASH/SCOUT_API_TOKENS or explicit SCOUT_AUTH=off.
  • Reverse proxy: Test with SCOUT_TRUST_PROXY=loopback and SCOUT_PUBLIC_ORIGIN=https://… behind a locally-hosted TLS proxy; verify client IP and HTTPS detection work correctly.
  • Debug API timeout: Run a slow query on /api/debug/query (e.g. SELECT * FROM listings LIMIT 999999999) and verify it returns a 400 error after ~5 seconds instead of blocking.
  • Browser SSRF: Check that rendered marketplace pages cannot reach http://127.0.0.1:* or WebSocket endpoints from browser dev tools.
  • Compose smoke test: Run npm run test:compose with SCOUT_SECRET and SCOUT_BROWSER_TOKEN set in .env; verify the Browserless health check passes and Scout starts.
  • Proxied-request rejection: Without credentials, make an API request with X-Forwarded-For: 1.2.3.4 (e.g. via curl -H) and confirm it receives 403.
  • npm run typecheck && npm test && npm run build && npm audit --omit=dev
## Summary - **Fail-closed auth:** Scout now refuses to start without credentials when listening beyond loopback, configured for a reverse proxy (`SCOUT_TRUST_PROXY` or `SCOUT_PUBLIC_ORIGIN` set), or when `SCOUT_AUTH=on` is explicit. This prevents unintended internet exposure of an unauthenticated instance. - **Proxied-request rejection:** Without credentials, Scout rejects any API request carrying `X-Forwarded-For`, `Forwarded`, `X-Real-IP`, or `CF-Connecting-IP` headers, so a proxy added later cannot publish it unprotected. - **Debug API time limits:** Queries now run in a child process with a 5-second timeout and 32 MB output limit per query, preventing runaway SQL from stalling the event loop or scheduler. - **Secret redaction:** Embedded encrypted secrets (e.g. within string concatenation in debug SQL) are now replaced with `[redacted: encrypted secret]`. - **Browser SSRF protection:** Rendered marketplace pages cannot request loopback, private, or single-label hostnames (blocking browser-based SSRF to internal services) or open WebSockets. - **Browserless token:** Compose now requires `SCOUT_BROWSER_TOKEN` (Browserless v2), preventing scripts on rendered pages from hijacking the browser. - **Hostname validation:** New `SCOUT_ALLOWED_HOSTS` accepts extra hostnames while auth is off (e.g. `scout.lan` over DNS rebinding); IP addresses and `localhost` are always accepted. - **Updated docs and examples:** Clearer guidance on authentication, reverse proxy setup, and requirements for Docker Compose and systemd deployments. ## Testing - **Local loopback mode:** Verify `npm run build` followed by `SCOUT_AUTH=off npm start` works without credentials on 127.0.0.1. - **Internet deployment:** Confirm Scout refuses to start when `SCOUT_HOST=0.0.0.0` (or publicly bound) without `SCOUT_PASSWORD_HASH`/`SCOUT_API_TOKENS` or explicit `SCOUT_AUTH=off`. - **Reverse proxy:** Test with `SCOUT_TRUST_PROXY=loopback` and `SCOUT_PUBLIC_ORIGIN=https://…` behind a locally-hosted TLS proxy; verify client IP and HTTPS detection work correctly. - **Debug API timeout:** Run a slow query on `/api/debug/query` (e.g. `SELECT * FROM listings LIMIT 999999999`) and verify it returns a 400 error after ~5 seconds instead of blocking. - **Browser SSRF:** Check that rendered marketplace pages cannot reach `http://127.0.0.1:*` or WebSocket endpoints from browser dev tools. - **Compose smoke test:** Run `npm run test:compose` with `SCOUT_SECRET` and `SCOUT_BROWSER_TOKEN` set in `.env`; verify the Browserless health check passes and Scout starts. - **Proxied-request rejection:** Without credentials, make an API request with `X-Forwarded-For: 1.2.3.4` (e.g. via `curl -H`) and confirm it receives 403. - **npm run typecheck && npm test && npm run build && npm audit --omit=dev**
Harden internet deployment: fail-closed auth behind proxies, SSRF and debug limits
Some checks failed
ci / verify (pull_request) Has been cancelled
901e8ecd41
- Refuse to start without credentials when SCOUT_TRUST_PROXY or
  SCOUT_PUBLIC_ORIGIN is set, and reject proxied API requests while auth is
  off unless SCOUT_AUTH=off; scout.service and .env.example set SCOUT_AUTH=on.
- Reject placeholder/low-entropy SCOUT_SECRET values whenever auth is on,
  Scout is proxied, or NODE_ENV=production.
- Restrict snapshot image downloads to marketplace CDNs, validate every
  redirect hop, and cap bodies while streaming; ntfy no longer follows
  redirects.
- Keep rendered marketplace pages off local/private hosts and WebSockets,
  block service workers, and stop passing Scout's env to a local Chromium.
  Compose moves to Browserless v2 with a required SCOUT_BROWSER_TOKEN.
- Debug API defaults off when auth is on; free-form SQL runs in a child
  process killed after 5 s, with broader secret redaction.
- Auth-off mode pins the Host header (SCOUT_ALLOWED_HOSTS) and rejects
  browser cross-site writes.
- Request timeout, backup retention (10), Discord digest markdown escaping,
  per-route CSP preserved, warning for untrusted forwarding peers, and
  Compose defaults SCOUT_TRUST_PROXY=uniquelocal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
kubus merged commit 2bea73ea24 into main 2026-09-30 15:11:42 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kubus/scout!14
No description provided.