Skip to content

Commit 3c20952

Browse files
richo-anyscaleedoakes
authored andcommitted
[core] Test for more browser-specific headers in dashboard browser rejection logic (ray-project#59042)
## Description Adds more headers to the denylist for recognising browser requests and denying them ## Related issues Supercedes ray-project#59040 Signed-off-by: Richo Healey <richo@anyscale.com> Signed-off-by: Edward Oakes <ed.nmi.oakes@gmail.com>
1 parent cdc1291 commit 3c20952

File tree

2 files changed

+247
-14
lines changed

2 files changed

+247
-14
lines changed

python/ray/dashboard/optional_utils.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,21 +130,31 @@ def _update_cache(task):
130130
def is_browser_request(req: Request) -> bool:
131131
"""Best-effort detection if the request was made by a browser.
132132
133-
Uses two heuristics:
133+
Uses three heuristics:
134134
1) If the `User-Agent` header starts with 'Mozilla'. This heuristic is weak,
135135
but hard for a browser to bypass e.g., fetch/xhr and friends cannot alter the
136136
user agent, but requests made with an HTTP library can stumble into this if
137137
they choose to user a browser-like user agent. At the time of writing, all
138138
common browsers' user agents start with 'Mozilla'.
139139
2) If any of the `Sec-Fetch-*` headers are present.
140+
3) If any of the various CORS headers are present
140141
"""
141142
return req.headers.get("User-Agent", "").startswith("Mozilla") or any(
142143
h in req.headers
143144
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.
144151
"Sec-Fetch-Mode",
145152
"Sec-Fetch-Dest",
146153
"Sec-Fetch-Site",
147154
"Sec-Fetch-User",
155+
# CORS headers specifying which other headers are modified
156+
"Access-Control-Request-Method",
157+
"Access-Control-Request-Headers",
148158
)
149159
)
150160

python/ray/dashboard/tests/test_dashboard.py

Lines changed: 236 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,234 @@ def test_browser_no_post_no_put(enable_test_module, ray_start_with_dashboard):
419419
webui_url = ray_start_with_dashboard["webui_url"]
420420
webui_url = format_web_url(webui_url)
421421

422+
testcases = (
423+
# chrome-invalid-tls.json
424+
{
425+
"Host": "localtest.me",
426+
"Connection": "keep-alive",
427+
"Content-Length": "0",
428+
"Sec-Ch-Ua-Platform": '"macOS"',
429+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
430+
"Sec-Ch-Ua": '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
431+
"Sec-Ch-Ua-Mobile": "?0",
432+
"Accept": "*/*",
433+
"Origin": "https://localtest.me",
434+
"Sec-Fetch-Site": "same-origin",
435+
"Sec-Fetch-Mode": "cors",
436+
"Sec-Fetch-Dest": "empty",
437+
"Referer": "https://localtest.me/",
438+
"Accept-Encoding": "gzip, deflate, br, zstd",
439+
"Accept-Language": "en-US,en;q=0.9",
440+
},
441+
# chrome-localhost-notls.json
442+
{
443+
"Host": "localhost:5000",
444+
"Connection": "keep-alive",
445+
"Content-Length": "0",
446+
"Sec-Ch-Ua-Platform": '"macOS"',
447+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
448+
"Sec-Ch-Ua": '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
449+
"Sec-Ch-Ua-Mobile": "?0",
450+
"Accept": "*/*",
451+
"Origin": "http://localhost:5000",
452+
"Sec-Fetch-Site": "same-origin",
453+
"Sec-Fetch-Mode": "cors",
454+
"Sec-Fetch-Dest": "empty",
455+
"Referer": "http://localhost:5000/",
456+
"Accept-Encoding": "gzip, deflate, br, zstd",
457+
"Accept-Language": "en-US,en;q=0.9",
458+
},
459+
# chrome-notlocalhost-notls.json
460+
{
461+
"Host": "localtest.me:5000",
462+
"Connection": "keep-alive",
463+
"Content-Length": "0",
464+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
465+
"Accept": "*/*",
466+
"Origin": "http://localtest.me:5000",
467+
"Referer": "http://localtest.me:5000/",
468+
"Accept-Encoding": "gzip, deflate",
469+
"Accept-Language": "en-US,en;q=0.9",
470+
},
471+
# chrome-notlocalhost-port80-notls.json
472+
{
473+
"Host": "localtest.me",
474+
"Connection": "keep-alive",
475+
"Content-Length": "0",
476+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
477+
"Accept": "*/*",
478+
"Origin": "http://localtest.me",
479+
"Referer": "http://localtest.me/",
480+
"Accept-Encoding": "gzip, deflate",
481+
"Accept-Language": "en-US,en;q=0.9",
482+
},
483+
# firefox-invalid-tls.json
484+
{
485+
"Host": "localtest.me",
486+
"User-Agent": "Fake",
487+
"Accept": "*/*",
488+
"Accept-Language": "en-US,en;q=0.5",
489+
"Accept-Encoding": "gzip, deflate, br, zstd",
490+
"Referer": "https://localtest.me/",
491+
"Origin": "https://localtest.me",
492+
"Connection": "keep-alive",
493+
"Sec-Fetch-Dest": "empty",
494+
"Sec-Fetch-Mode": "cors",
495+
"Sec-Fetch-Site": "same-origin",
496+
"Priority": "u=0",
497+
"Content-Length": "0",
498+
},
499+
# firefox-localhost-notls.json
500+
{
501+
"Host": "localhost:5000",
502+
"User-Agent": "Fake",
503+
"Accept": "*/*",
504+
"Accept-Language": "en-US,en;q=0.5",
505+
"Accept-Encoding": "gzip, deflate, br, zstd",
506+
"Referer": "http://localhost:5000/",
507+
"Origin": "http://localhost:5000",
508+
"Connection": "keep-alive",
509+
"Sec-Fetch-Dest": "empty",
510+
"Sec-Fetch-Mode": "cors",
511+
"Sec-Fetch-Site": "same-origin",
512+
"Priority": "u=0",
513+
"Content-Length": "0",
514+
},
515+
# firefox-notlocalhost-notls.json
516+
{
517+
"Host": "localtest.me:5000",
518+
"User-Agent": "Fake",
519+
"Accept": "*/*",
520+
"Accept-Language": "en-US,en;q=0.5",
521+
"Accept-Encoding": "gzip, deflate",
522+
"Referer": "http://localtest.me:5000/",
523+
"Origin": "http://localtest.me:5000",
524+
"Connection": "keep-alive",
525+
"Priority": "u=0",
526+
"Content-Length": "0",
527+
},
528+
# firefox-notlocalhost-port80-notls.json
529+
{
530+
"Host": "localtest.me",
531+
"User-Agent": "Fake",
532+
"Accept": "*/*",
533+
"Accept-Language": "en-US,en;q=0.5",
534+
"Accept-Encoding": "gzip, deflate",
535+
"Referer": "http://localtest.me/",
536+
"Origin": "http://localtest.me",
537+
"Connection": "keep-alive",
538+
"Priority": "u=0",
539+
"Content-Length": "0",
540+
},
541+
# safari-invalid-tls.json
542+
{
543+
"Host": "localtest.me",
544+
"Accept": "*/*",
545+
"Origin": "https://localtest.me",
546+
"Sec-Fetch-Site": "same-origin",
547+
"Sec-Fetch-Mode": "cors",
548+
"User-Agent": "Fake",
549+
"Referer": "https://localtest.me/",
550+
"Sec-Fetch-Dest": "empty",
551+
"Content-Length": "0",
552+
"Accept-Language": "en-US,en;q=0.9",
553+
"Priority": "u=3, i",
554+
"Accept-Encoding": "gzip, deflate, br",
555+
"Connection": "keep-alive",
556+
},
557+
# safari-localhost-notls.json
558+
{
559+
"Host": "localhost:5000",
560+
"Accept": "*/*",
561+
"Origin": "http://localhost:5000",
562+
"Sec-Fetch-Site": "same-origin",
563+
"Sec-Fetch-Mode": "cors",
564+
"User-Agent": "Fake",
565+
"Referer": "http://localhost:5000/",
566+
"Sec-Fetch-Dest": "empty",
567+
"Content-Length": "0",
568+
"Accept-Language": "en-US,en;q=0.9",
569+
"Priority": "u=3, i",
570+
"Accept-Encoding": "gzip, deflate",
571+
"Connection": "keep-alive",
572+
},
573+
# safari-notlocalhost-notls.json
574+
{
575+
"Host": "localtest.me:5000",
576+
"User-Agent": "Fake",
577+
"Accept": "*/*",
578+
"Content-Length": "0",
579+
"Referer": "http://localtest.me:5000/",
580+
"Origin": "http://localtest.me:5000",
581+
"Accept-Language": "en-US,en;q=0.9",
582+
"Priority": "u=3, i",
583+
"Accept-Encoding": "gzip, deflate",
584+
"Connection": "keep-alive",
585+
},
586+
# safari-notlocalhost-port80-notls.json
587+
{
588+
"Host": "localtest.me",
589+
"User-Agent": "Fake",
590+
"Accept": "*/*",
591+
"Content-Length": "0",
592+
"Referer": "http://localtest.me/",
593+
"Origin": "http://localtest.me",
594+
"Accept-Language": "en-US,en;q=0.9",
595+
"Priority": "u=3, i",
596+
"Accept-Encoding": "gzip, deflate",
597+
"Connection": "keep-alive",
598+
},
599+
# edge-valid-tls.json
600+
{
601+
"Content-Length": "0",
602+
"Sec-Ch-Ua-Platform": '"Windows"',
603+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0",
604+
"Sec-Ch-Ua": '"Chromium";v="142", "Microsoft Edge";v="142", "Not_A Brand";v="99"',
605+
"Sec-Ch-Ua-Mobile": "?0",
606+
"Accept": "*/*",
607+
"Origin": "https://testing-dark-field-5895.fly.dev",
608+
"Sec-Fetch-Site": "same-origin",
609+
"Sec-Fetch-Mode": "cors",
610+
"Sec-Fetch-Dest": "empty",
611+
"Referer": "https://testing-dark-field-5895.fly.dev/",
612+
"Accept-Encoding": "gzip, deflate, br, zstd",
613+
"Accept-Language": "en-US,en;q=0.9",
614+
"Priority": "u=1, i",
615+
"X-Request-Start": "t=1764259434792741",
616+
"Host": "testing-dark-field-5895.fly.dev",
617+
},
618+
# edge-notlocalhost-notls
619+
{
620+
"Host": "localtest.me:5000",
621+
"Connection": "keep-alive",
622+
"Content-Length": "0",
623+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0",
624+
"Accept": "*/*",
625+
"Origin": "http://localtest.me:5000",
626+
"Referer": "http://localtest.me:5000/",
627+
"Accept-Encoding": "gzip, deflate",
628+
"Accept-Language": "en-US,en;q=0.9",
629+
},
630+
# edge-localhost-notls
631+
{
632+
"Host": "localhost:5000",
633+
"Connection": "keep-alive",
634+
"Content-Length": "0",
635+
"Sec-Ch-Ua-Platform": '"Windows"',
636+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0",
637+
"Sec-Ch-Ua": '"Chromium";v="142", "Microsoft Edge";v="142", "Not_A Brand";v="99"',
638+
"Sec-Ch-Ua-Mobile": "?0",
639+
"Accept": "*/*",
640+
"Origin": "http://localhost:5000",
641+
"Sec-Fetch-Site": "same-origin",
642+
"Sec-Fetch-Mode": "cors",
643+
"Sec-Fetch-Dest": "empty",
644+
"Referer": "http://localhost:5000/",
645+
"Accept-Encoding": "gzip, deflate, br, zstd",
646+
"Accept-Language": "en-US,en;q=0.9",
647+
},
648+
)
649+
422650
def dashboard_available():
423651
try:
424652
return requests.get(webui_url).status_code == 200
@@ -434,19 +662,14 @@ def dashboard_available():
434662
response.raise_for_status()
435663

436664
# Starting job should be blocked for browsers
437-
response = requests.post(
438-
webui_url + "/api/jobs/",
439-
json={"entrypoint": "ls"},
440-
headers={
441-
"User-Agent": (
442-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
443-
"AppleWebKit/537.36 (KHTML, like Gecko) "
444-
"Chrome/119.0.0.0 Safari/537.36"
445-
)
446-
},
447-
)
448-
with pytest.raises(HTTPError):
449-
response.raise_for_status()
665+
for testcase in testcases:
666+
response = requests.post(
667+
webui_url + "/api/jobs/",
668+
json={"entrypoint": "ls"},
669+
headers=testcase,
670+
)
671+
with pytest.raises(HTTPError):
672+
response.raise_for_status()
450673

451674
# Getting jobs should be fine for browsers
452675
response = requests.get(webui_url + "/api/jobs/")

0 commit comments

Comments
 (0)