Skip to content

Commit 9ac1e61

Browse files
edoakesricho-anyscaleaslonnie
authored
Cherry pick browser request validation improvements (#59045)
Cherry pick #58553 #58648 #59042 --------- Signed-off-by: Richo Healey <richo@anyscale.com> Signed-off-by: Edward Oakes <ed.nmi.oakes@gmail.com> Co-authored-by: richo-anyscale <richo@anyscale.com> Co-authored-by: Lonnie Liu <95255098+aslonnie@users.noreply.github.com>
1 parent fd7bc1a commit 9ac1e61

File tree

3 files changed

+323
-43
lines changed

3 files changed

+323
-43
lines changed

python/ray/dashboard/http_server_head.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ async def path_clean_middleware(self, request, handler):
183183
@aiohttp.web.middleware
184184
async def browsers_no_post_put_middleware(self, request, handler):
185185
if (
186-
# A best effort test for browser traffic. All common browsers
187-
# start with Mozilla at the time of writing.
186+
# Deny mutating requests from browsers.
187+
# See `is_browser_request` for details of the check.
188188
dashboard_optional_utils.is_browser_request(request)
189189
and request.method in [hdrs.METH_POST, hdrs.METH_PUT]
190190
):

python/ray/dashboard/optional_utils.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,18 +128,39 @@ def _update_cache(task):
128128

129129

130130
def is_browser_request(req: Request) -> bool:
131-
"""Checks if a request is made by a browser like user agent.
132-
133-
This heuristic is very weak, but hard for a browser to bypass- eg,
134-
fetch/xhr and friends cannot alter the user-agent, but requests made with
135-
an http library can stumble into this if they choose to user a browser like
136-
user agent.
131+
"""Best-effort detection if the request was made by a browser.
132+
133+
Uses three heuristics:
134+
1) If the `User-Agent` header starts with 'Mozilla'. This heuristic is weak,
135+
but hard for a browser to bypass e.g., fetch/xhr and friends cannot alter the
136+
user agent, but requests made with an HTTP library can stumble into this if
137+
they choose to user a browser-like user agent. At the time of writing, all
138+
common browsers' user agents start with 'Mozilla'.
139+
2) If any of the `Sec-Fetch-*` headers are present.
140+
3) If any of the various CORS headers are present
137141
"""
138-
return req.headers["User-Agent"].startswith("Mozilla")
142+
return req.headers.get("User-Agent", "").startswith("Mozilla") or any(
143+
h in req.headers
144+
for h in (
145+
# Origin and Referer are sent by browser user agents to give
146+
# information about the requesting origin
147+
"Referer",
148+
"Origin",
149+
# Sec-Fetch headers are sent with many but not all `fetch`
150+
# requests, and will eventually be sent on all requests.
151+
"Sec-Fetch-Mode",
152+
"Sec-Fetch-Dest",
153+
"Sec-Fetch-Site",
154+
"Sec-Fetch-User",
155+
# CORS headers specifying which other headers are modified
156+
"Access-Control-Request-Method",
157+
"Access-Control-Request-Headers",
158+
)
159+
)
139160

140161

141162
def deny_browser_requests() -> Callable:
142-
"""Reject any requests that appear to be made by a browser"""
163+
"""Reject any requests that appear to be made by a browser."""
143164

144165
def decorator_factory(f: Callable) -> Callable:
145166
@functools.wraps(f)
@@ -149,6 +170,7 @@ async def decorator(self, req: Request):
149170
text="Browser requests not allowed",
150171
status=aiohttp.web.HTTPMethodNotAllowed.status_code,
151172
)
173+
152174
return await f(self, req)
153175

154176
return decorator

0 commit comments

Comments
 (0)