[runtime_env] Block browser requests on the runtime env agent HTTP server - #65255
[runtime_env] Block browser requests on the runtime env agent HTTP server#65255SashaMIT wants to merge 1 commit into
Conversation
…rver The runtime env agent binds a TCP socket on the node IP and serves state-changing endpoints (e.g. /get_or_create_runtime_env triggers package installation), but unlike both sibling dashboard HTTP servers (dashboard head and dashboard agent) it does not apply the browser-request middleware that blocks DNS rebinding / CSRF style traffic. When token authentication is not enabled (the default; the token middleware is a documented no-op without it), the agent has no browser protection at all while its siblings do. Add the same middleware to the runtime env agent for parity. Because ray._private cannot import ray.dashboard in minimal installations, the generic browser-request detection/middleware is provided from a new ray._private.browser_request_middleware module; converging dashboard.optional_utils onto it is intentionally left out of scope to keep this change minimal. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: SashaMIT <sash@ela.city> Co-authored-by: Cursor <cursoragent@cursor.com>
ad21f7b to
4fed92b
Compare
There was a problem hiding this comment.
Code Review
This pull request introduces a browser-request detection and blocking middleware to prevent DNS rebinding and CSRF attacks on internal Ray HTTP servers, and applies it to the runtime environment agent. Feedback suggests optimizing the middleware by converting the allowed paths list to a set during initialization to avoid O(N) lookups on every incoming request.
| allowed_methods = allowed_methods or set() | ||
|
|
||
| @aiohttp_module.web.middleware | ||
| async def browser_request_middleware(request, handler): | ||
| if not is_browser_request(request): | ||
| return await handler(request) | ||
|
|
||
| # Allow whitelisted paths to bypass the check | ||
| if allowed_paths and request.path in allowed_paths: | ||
| return await handler(request) |
There was a problem hiding this comment.
Converting allowed_paths to a set during middleware initialization avoids performing an
| allowed_methods = allowed_methods or set() | |
| @aiohttp_module.web.middleware | |
| async def browser_request_middleware(request, handler): | |
| if not is_browser_request(request): | |
| return await handler(request) | |
| # Allow whitelisted paths to bypass the check | |
| if allowed_paths and request.path in allowed_paths: | |
| return await handler(request) | |
| allowed_methods = allowed_methods or set() | |
| allowed_paths_set = set(allowed_paths) if allowed_paths else set() | |
| @aiohttp_module.web.middleware | |
| async def browser_request_middleware(request, handler): | |
| if not is_browser_request(request): | |
| return await handler(request) | |
| # Allow whitelisted paths to bypass the check | |
| if request.path in allowed_paths_set: | |
| return await handler(request) |
There was a problem hiding this comment.
Noted. At the only current call site (runtime env agent) allowed_paths is None, so the membership check never runs; leaving the parameter as a list keeps the public signature simple for future callers. Happy to switch to a frozenset built at middleware-construction time if a second call site lands or if you'd prefer it anyway.
Description
The runtime env agent (
ray._private.runtime_env.agent) binds a TCP socket on the node IP and serves state-changing endpoints — notablyPOST /get_or_create_runtime_env, which triggers runtime environment creation (pip/conda package installation, setup commands).Both sibling Ray HTTP servers apply a browser-request middleware whose documented purpose is to "block browser requests to prevent DNS rebinding and CSRF attacks":
http_server_head.py)http_server_agent.py, with the comment# Block all browser requests - agent is only accessed internally)The runtime env agent applies only
get_token_auth_middleware— which is a documented no-op when token authentication is not enabled (the default configuration) — and no browser-request middleware. So in default deployments this agent is the one Ray HTTP server with no browser/DNS-rebinding protection at all, despite being equally internal-only and arguably the most sensitive (package installation).This PR applies the same browser-request middleware to the runtime env agent for parity with its siblings.
Because
ray._privatecomponents cannot importray.dashboard(not import-safe in minimal installations), the generic detection/middleware is provided by a newray._private.browser_request_middlewaremodule with the same logic asdashboard.optional_utils. Convergingoptional_utilsonto the private module is intentionally left out of scope to keep this change minimal; happy to do that here if preferred.Checks
scripts/format.shto lint the changes in this PR. (could not run locally — bazel toolchain not available on this machine; logic mirrors the existing dashboard middleware and compiles clean)Verification performed:
py_compileon both changed files; standalone behavioral smoke test of the middleware (browser UA /Sec-Fetch-*/Originrequests → 403; internal non-browser client → passes through). Relying on CI for the full suite.Happy to route this via security@anyscale.com instead if you'd rather treat the middleware asymmetry as a security report — filing as a hardening-parity PR since the protection is already documented and deployed on the sibling servers.
Made with Cursor