Repository navigation
fix: harden HFSandboxProvider local auth proxy - #1558
Conversation
- Reject upstream redirects instead of following them: HTTP requests use allow_redirects=False (3xx becomes a generic 502) and WebSocket handshakes disable websockets' redirect following via process_redirect, which would otherwise replay the injected auth headers to another origin and could downgrade https to http. - Sanitize WebSocket handshake failures: the route catches them, logs a generic warning without the exception chain (which can embed credentials in the target URL) and denies the client. - stop() sets force_exit to bound the drain, and if the thread survives the join it keeps its handles and raises, so cleanup can retry instead of leaking a thread that keeps the loopback port bound. stop_container() still kills the sandbox first and re-raises after. - start_container() refuses a restart while a proxy from a failed stop is still retained, so the old thread cannot be overwritten and lost. Adds a dedicated regression test suite (faked pool, loopback fake upstream, no HF account or network needed). Signed-off-by: dongjiang1989 <dongjiang1989@126.com>
The HF proxy hardening applies to code already on main and lands separately in openenv#1558 with its own regression suite, keeping this PR focused on the new provider. Signed-off-by: dongjiang1989 <dongjiang1989@126.com>
burtenshaw
left a comment
There was a problem hiding this comment.
src/openenv/core/containers/runtime/hf_sandbox_provider.py at 94e607e3 is identical to the version reviewed on #1556 at 0eda21ab.
That review verified it against a local HTTPS router and recorders with websockets 15.0.1 and 16.0. An HTTP 307 to a plaintext recorder and a WSS handshake 302 to a different TLS origin are both rejected, and the recorder receives no request, body or credential header. As a control, the default WebSocket connector follows the same 302 and replays Authorization. Failed handshakes return a generic denial, and captured log records and exception chains contain no upstream URL or token. Normal authenticated HTTP traffic, query strings, WebSocket text and downstream binary frames pass. stop() bounds its join and keeps the server and thread handles when the thread is still alive, and the _proxy is not None start guard refuses a restart until cleanup succeeds.
The new tests/test_core/test_hf_sandbox_provider.py adds the first regression suite for this provider. No blocking findings; this can merge once CI passes.
Summary
Hardens
_LocalAuthProxyinhf_sandbox_provider.py, split out of the review discussion in #1556 (finding: the same proxy code is copied there, and the issues apply to code already on main).allow_redirects=False(3xx becomes a generic 502); WebSocket handshakes disablewebsockets' redirect following viaprocess_redirect. Following a redirect replays the injected auth headers (and POST bodies) to another origin and can downgrade https to http. Verified against websockets 15.0.1 and 16.0: the default connector does replayadditional_headerscross-origin..claude/docs/INVARIANTS.mdcredential rule).stop()setsforce_exitto bound the drain and raisesRuntimeErrorif the thread survives the join, instead of dropping the handles and leaking a bound loopback port.stop_container()still kills the sandbox first and re-raises after._proxy, so a retained (possibly still bound) proxy cannot be overwritten and lost.Adds
tests/test_core/test_hf_sandbox_provider.py— a dedicated regression suite for the provider and proxy (faked pool + loopback fake upstream, no HF account or network): header injection over HTTP/WS, HTTP 307 and cross-origin WS 302 rejection with an attacker recorder, handshake log sanitization, stuck-thread retention, kill-despite-proxy-failure, restart guard, double-start rejection.Type of Change
Alignment Checklist
Before submitting, verify:
.claude/docs/PRINCIPLES.mdand this PR aligns with our principles.claude/docs/INVARIANTS.mdand no invariants are violated/pre-submit-pr(orbash .claude/hooks/lint.shand tests) and addressed all issuesRFC Status
Test Plan
PYTHONPATH=src:envs uv run pytest tests/test_core/test_hf_sandbox_provider.py -v: 10 passed, no HF account neededPYTHONPATH=src:envs uv run pytest tests/test_core/ -q: 632 passed, 16 skipped (4 pre-existing local failures intest_generic_client.pyfrom Docker occupying port 8000; they reproduce on main)usort,ruff format --check,ruff checkclean on both changed filesHFSandboxProvider, verify/health,echo_messageround-trip, and cleanstop_container()Claude Code Review
N/A — changes were driven by maintainer review in #1556 and verified with real-transport probes (HTTPS router, plaintext and TLS recorders, websockets 15.0.1/16.0).
Note for #1556: the
AgentSandboxProviderproxy copy there will match this file once this PR lands; that PR drops itshf_sandbox_provider.pyedits accordingly.Note
High Risk
Changes authentication proxy behavior (redirect rejection and error sanitization) in a path that injects HF credentials; mistakes could leak tokens or break sandbox connectivity.
Overview
Hardens
_LocalAuthProxyinhf_sandbox_provider.pyso injected Hugging Face auth headers cannot be replayed to another origin via redirects, and so failures do not leak credentials in logs or client errors.Redirect handling: HTTP upstream calls use
allow_redirects=False; 3xx responses become a generic 502 instead of following the redirect (which would resend auth headers and bodies, and could downgrade HTTPS). WebSocket handshakes disablewebsocketsredirect following viaprocess_redirect, for the same cross-origin header replay reason.WebSocket errors: Upstream handshake failures are caught in the route, logged with a generic warning only, and the client is closed without propagating exception text that may embed secrets in URLs.
Lifecycle:
stop()setsforce_exit, raisesRuntimeErrorif the proxy thread does not exit within 5s (keeping server/thread handles for retry instead of leaking a bound loopback port).HFSandboxProviderblocksstart_container()while a proxy handle still awaits cleanup, andstop_container()still kills the sandbox when proxy shutdown fails, then re-raises the proxy error.Adds
tests/test_core/test_hf_sandbox_provider.py— loopback fakes (no HF account): header injection, HTTP/WS redirect rejection against a recorder “attacker”, sanitized handshake logging, stuck-thread behavior, and provider start/stop guards.Reviewed by Cursor Bugbot for commit 94e607e. Bugbot is set up for automated code reviews on this repo. Configure here.