From 40fc69b1cf030c779d031468fb60a1fa288fb8b3 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Mon, 22 Jun 2026 00:22:44 -0400 Subject: [PATCH] Require real browser origins for escape grant minting (#4582) --- api/routes.py | 4 ++-- ...t_issue4582_workspace_escape_navigation.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/api/routes.py b/api/routes.py index 2b931382..9da2ae13 100644 --- a/api/routes.py +++ b/api/routes.py @@ -12949,8 +12949,8 @@ def _read_json_request_body(handler, *, max_bytes: int = 4096) -> dict: def _handle_escape_authorize(handler, parsed, body: dict | None = None): if handler.command != "POST": return bad(handler, "method not allowed", 405) - if not _is_browser_unsafe_request(handler): - return bad(handler, "browser confirmation required", 403) + if not handler.headers.get("Origin"): + return bad(handler, "browser origin required", 403) if not _check_csrf(handler): return bad(handler, _csrf_rejection_error(handler), 403) if body is None: diff --git a/tests/test_issue4582_workspace_escape_navigation.py b/tests/test_issue4582_workspace_escape_navigation.py index 0096493f..ad2d3882 100644 --- a/tests/test_issue4582_workspace_escape_navigation.py +++ b/tests/test_issue4582_workspace_escape_navigation.py @@ -36,6 +36,11 @@ def _browser_headers() -> dict[str, str]: return {"Origin": f"{parsed.scheme}://{parsed.netloc}"} +def _referer_only_headers() -> dict[str, str]: + parsed = urllib.parse.urlparse(BASE) + return {"Referer": f"{parsed.scheme}://{parsed.netloc}/workspace"} + + def _post_json(path: str, body: dict | None = None, headers: dict[str, str] | None = None) -> tuple[dict, int]: req = urllib.request.Request( BASE + path, @@ -101,6 +106,13 @@ class TestIssue4582EscapeNavigationLive: ) assert denied_status == 403, denied + referer_only, referer_only_status = _post_json( + "/api/escape/authorize", + {"session_id": sid, "path": "escape-file.txt"}, + headers=_referer_only_headers(), + ) + assert referer_only_status == 403, referer_only + auth, status = _post_json( "/api/escape/authorize", {"session_id": sid, "path": "escape-file.txt"}, @@ -147,6 +159,13 @@ class TestIssue4582EscapeNavigationLive: denied, denied_status = _post_json("/api/escape/authorize", {"session_id": sid, "path": "escape"}) assert denied_status == 403, denied + referer_only, referer_only_status = _post_json( + "/api/escape/authorize", + {"session_id": sid, "path": "escape"}, + headers=_referer_only_headers(), + ) + assert referer_only_status == 403, referer_only + auth, status = _post_json( "/api/escape/authorize", {"session_id": sid, "path": "escape"},