Skip to content

[runtime_env] Block browser requests on the runtime env agent HTTP server - #65255

Open
SashaMIT wants to merge 1 commit into
ray-project:masterfrom
SashaMIT:fix/runtime-env-agent-browser-middleware
Open

[runtime_env] Block browser requests on the runtime env agent HTTP server#65255
SashaMIT wants to merge 1 commit into
ray-project:masterfrom
SashaMIT:fix/runtime-env-agent-browser-middleware

Conversation

@SashaMIT

@SashaMIT SashaMIT commented Aug 6, 2026

Copy link
Copy Markdown

Description

The runtime env agent (ray._private.runtime_env.agent) binds a TCP socket on the node IP and serves state-changing endpoints — notably POST /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":

  • dashboard head (http_server_head.py)
  • dashboard agent (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._private components cannot import ray.dashboard (not import-safe in minimal installations), the generic detection/middleware is provided by a new ray._private.browser_request_middleware module with the same logic as dashboard.optional_utils. Converging optional_utils onto the private module is intentionally left out of scope to keep this change minimal; happy to do that here if preferred.

Checks

  • I've signed off every commit (by submitting pull requests, you agree to the Developer Certificate of Origin)
  • I've run scripts/format.sh to 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)
  • I've included any doc changes needed
  • I've made sure the tests are passing.

Verification performed: py_compile on both changed files; standalone behavioral smoke test of the middleware (browser UA / Sec-Fetch-* / Origin requests → 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

@SashaMIT
SashaMIT requested a review from a team as a code owner August 6, 2026 10:46
…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>
@SashaMIT
SashaMIT force-pushed the fix/runtime-env-agent-browser-middleware branch from ad21f7b to 4fed92b Compare August 6, 2026 10:47

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +70 to +79
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Converting allowed_paths to a set during middleware initialization avoids performing an $O(N)$ list lookup on every incoming HTTP request. This improves the efficiency of the middleware.

Suggested change
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)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ray-gardener ray-gardener Bot added core Issues that should be addressed in Ray Core community-contribution Contributed by the community labels Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution Contributed by the community core Issues that should be addressed in Ray Core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant