fix(server): add 30s connection timeout to prevent slow-client thread exhaustion - #198
Conversation
…d exhaustion Set Handler.timeout = 30. Python's BaseHTTPRequestHandler.setup() calls self.request.settimeout(timeout), which raises socket.timeout on idle or slow connections after the configured duration. This defends against Slowloris-style attacks where a client holds connections open indefinitely, exhausting threads in ThreadingHTTPServer. Also recovers threads from crashed clients with hung TCP connections. Addresses nesquena#194.
|
Minimal and correct. What this protects against:
30 seconds is a reasonable default for a local/LAN tool. It's long enough that no normal browser request will be affected, and short enough to reclaim threads from hung connections within half a minute. One thing to note: The single-file, single-line change is easy to audit. Ready for maintainer review. |
Full Review: PR #198 — 30s connection timeoutThanks @iRonin! Minimal and correct. Security AuditClean. One line added — Code ReviewProtects against:
30 seconds is a reasonable balance — long enough for slow legitimate connections, short enough to reclaim resources from stalled ones. SSE streams are unaffected because they actively write data (the timeout is on idle read, not on the connection lifetime). Test Results506 passed, 0 failed, 41 skipped. No regressions. VerdictApproved. Ready to merge. |
…d exhaustion (nesquena#198) Set Handler.timeout = 30. Python's BaseHTTPRequestHandler.setup() calls self.request.settimeout(timeout), which raises socket.timeout on idle or slow connections after the configured duration. This defends against Slowloris-style attacks where a client holds connections open indefinitely, exhausting threads in ThreadingHTTPServer. Also recovers threads from crashed clients with hung TCP connections. Addresses nesquena#194.
…ore 6 upstream tests with FOX_OVERLAY skipif (#30) Phase 7a (fork side) of v0.6.0 upstream-separation migration (fox-in-the-box-ai/fox-in-the-box#155). Closes nesquena#197. ## What this removes * `api/onboarding.py` (283 LOC) — Fox's wholesale-replaced 3-step setup wizard. Moves to fox_overlay/webui_modules/onboarding.py in the monorepo (P7b nesquena#198). * `static/setup.html`, `static/setup.css`, `static/setup.js` — deferred from Phase 2 because they were coupled to api/onboarding.py's `REPO_ROOT / "static" / "setup.html"` path access. P7b moves them to the overlay's webui_static/. ## What this restores 6 upstream onboarding tests Fox previously deleted (the deletion predates the v0.6.0 migration). All restored from merge-base 9e31a2a with a module-level `pytestmark = pytest.mark.skipif(FOX_OVERLAY)` decorator so they: * Pass fork CI today (skip cleanly when FOX_OVERLAY=1, which is the fork test env post-overlay) * Run normally against virgin upstream content (post-Phase-8 re-point) where Fox doesn't replace onboarding Restored: * `tests/test_issue1499_keyless_onboarding.py` * `tests/test_issue1499_onboarding_probe.py` * `tests/test_onboarding_existing_config.py` * `tests/test_onboarding_mvp.py` * `tests/test_onboarding_network.py` * `tests/test_onboarding_static.py` ## What this does NOT change `api/routes.py` keeps its 6 inline onboarding handler blocks (lines 1641-1648 + 2310-2326) following the Option E pattern from Phase 4. The dispatcher hook (Phase 4) pre-empts /setup and /api/setup/* before those lazy imports trigger — so they become dead code after P7b ships, but stay in source until a later cleanup pass (Phase 8+). ## Critical preservation Fox's `_write_env_key` (line 93 of deleted onboarding.py) is used by overlay's `webui_modules/hostname.py` — re-exported in P7b's overlay onboarding module + hostname.py import updated to point at the new location. **P7b must ship simultaneously with this PR's submodule bump** to avoid hostname module-load ImportError. ## Sequencing This PR merges FIRST. Monorepo DRAFT (P7b) bumps submodule + ships overlay onboarding + .fox-removals consumer wiring. ## Diff summary ``` api/onboarding.py | 283 ----- (deleted) static/setup.css | (deleted) static/setup.html | (deleted) static/setup.js | (deleted) tests/test_issue1499_keyless_onboarding.py | + (restored) tests/test_issue1499_onboarding_probe.py | + (restored) tests/test_onboarding_existing_config.py | + (restored) tests/test_onboarding_mvp.py | + (restored) tests/test_onboarding_network.py | + (restored) tests/test_onboarding_static.py | + (restored) ```
…d exhaustion (nesquena#198) Set Handler.timeout = 30. Python's BaseHTTPRequestHandler.setup() calls self.request.settimeout(timeout), which raises socket.timeout on idle or slow connections after the configured duration. This defends against Slowloris-style attacks where a client holds connections open indefinitely, exhausting threads in ThreadingHTTPServer. Also recovers threads from crashed clients with hung TCP connections. Addresses nesquena#194.
Summary
Adds a 30-second read timeout to the HTTP handler by setting
timeout = 30on theHandlerclass. PythonBaseHTTPRequestHandler.setup()callsself.request.settimeout(self.timeout), which causesrfile.read()to raisesocket.timeoutafter the configured duration on idle or slow connections.Why
The
ThreadingHTTPServerspawns one thread per connection. With no timeout:The Fix
One line in
server.py:This is the standard Python stdlib approach — no new dependencies, no architectural change.
Fixes #194