fix(security): drop PASSWORD_SECRET from proxy env + add slowloris read-timeout (#101 P0) - #102
Merged
Merged
Conversation
…ad-timeout Two P0 unauthenticated-security fixes from the #101 code review. #1 (entrypoint.sh): `runuser` (without --login) inherits the caller's whole environment, so an operator-supplied `-e PASSWORD_SECRET=...` landed in the proxy's /proc/<pid>/environ — readable by any TTYD_USER process and enough to forge session/CSRF tokens. `unset PASSWORD_SECRET` after writing the secret file and before launching the proxy makes the mode-400 file the only channel, as CLAUDE.md guarantees. #2 (app/ttydproxy): BaseHTTPRequestHandler had no read timeout, so a slow client (slowloris) pinned a ThreadingHTTPServer worker forever (the login rate-limiter only fires after the slow window). Add a config-driven REQUEST_TIMEOUT (default 30s, min 1) applied as the handler's `timeout` class attribute. Implemented in config.py + app.py (the handler subclass) rather than the stdlib-only server.py so the value stays env-configurable and validated. Closes #101 findings #1, #2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgvJhzd8gEhXVE4MVu18nn
Owner
Author
🔍 Local review (cycle 1)Reviewed locally (Claude subagent + Codex companion), no bots pinged. Both reviewers ran; findings triaged against the real code + git history.
No
Both are recorded as valid follow-up hardening ideas. This completes the local review cycle for #102 — merge is left to the maintainer. |
Non-blocking review nitpick from the local review of #102: the timeout test only asserted `> 0`. Also assert `TTYDProxyHandler.timeout == config.REQUEST_TIMEOUT` so a regression that hard-codes or drops the config wiring is caught. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgvJhzd8gEhXVE4MVu18nn
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two P0 unauthenticated-security fixes from the multi-agent code review in #101.
PASSWORD_SECRETleaked into the proxy's environment (entrypoint.sh).runuser(without--login) inherits the caller's whole environment, so an operator-supplied-e PASSWORD_SECRET=...landed in the proxy's/proc/<pid>/environ— readable by anyTTYD_USERprocess (including terminal shells) and enough to forge session/CSRF tokens.unset PASSWORD_SECRETafter writing the secret file and before launching the proxy makes the mode-400 file the only channel, as CLAUDE.md guarantees.app/ttydproxy).BaseHTTPRequestHandlerhad no read timeout, so a slow client pinned aThreadingHTTPServerworker forever (the login rate-limiter only fires after the slow window). Added a config-drivenREQUEST_TIMEOUT(default 30s, min 1) applied as the handler'stimeoutclass attribute.#101 findings closed
New/changed env vars
REQUEST_TIMEOUT— per-connection socket read timeout in seconds (default 30, min 1). Read inconfig.py, validated viaenv_int. Documented in.env.exampleby PR-B (which owns that file in this split).Note: finding #2 targets
app/server.pyin the review, but the fix lives inconfig.py+app.py(the handler subclass carries thetimeoutattribute) so the value stays env-configurable and validated, rather than editing the stdlib-onlyserver.py.Ports / volumes
No changes.
Tests
New:
test_password_secret_unset_before_proxy_launch(ordering assertion intest_shell_scripts.py),REQUEST_TIMEOUTconfig + handler-attribute tests intest_config_timeouts.py. Fullpytest tests/green;bash -n entrypoint.shclean.🤖 Generated with Claude Code
https://claude.ai/code/session_01KgvJhzd8gEhXVE4MVu18nn