This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new eeeb6f02a88 reject backslash-after-scheme urls in is_safe_url (#70215)
eeeb6f02a88 is described below
commit eeeb6f02a88791f3c1f9b14c92e3b0b70c5d38a0
Author: Samina <[email protected]>
AuthorDate: Mon Jul 27 15:59:53 2026 +0530
reject backslash-after-scheme urls in is_safe_url (#70215)
---
airflow-core/src/airflow/api_fastapi/core_api/security.py | 9 +++++----
airflow-core/tests/unit/api_fastapi/core_api/test_security.py | 8 ++++++++
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/security.py
b/airflow-core/src/airflow/api_fastapi/core_api/security.py
index 5f85b68e6b1..720423797b5 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/security.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/security.py
@@ -1007,12 +1007,13 @@ def is_safe_url(target_url: str, request: Request |
None = None) -> bool:
# According to WHATWG for http/https /// is interpreted as // whereas
urllib doesnt
# this leads to an inconsistency where python returns a target url with
/// as a valid url
- # The same thing also happens with \ where under WHATWG \ are translated
to /
- target_url = unquote(target_url).strip()
- if target_url.startswith(("//", "/\\", "\\/", "\\\\")):
+ # The same thing also happens with \ where under WHATWG \ are translated
to /, including
+ # after a scheme, so "https:\\host" is an authority for a browser but a
path for urllib.
+ target_url = unquote(target_url).strip().replace("\\", "/")
+ if target_url.startswith("//"):
return False
for base_url, parsed_base in parsed_bases:
- parsed_target = urlparse(urljoin(base_url, unquote(target_url))) #
Resolves relative URLs
+ parsed_target = urlparse(urljoin(base_url, target_url)) # Resolves
relative URLs
base_path = parsed_base.path or "/"
target_path = parsed_target.path or "/"
diff --git a/airflow-core/tests/unit/api_fastapi/core_api/test_security.py
b/airflow-core/tests/unit/api_fastapi/core_api/test_security.py
index d7614f82539..8824969f976 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/test_security.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/test_security.py
@@ -632,6 +632,14 @@ class TestFastApiSecurity:
("\\\\some_netlock.com/prefix", False),
# encoded url
("%5C%5C%5C%5Csome_netlock.com/prefix", False),
+ # \ after the scheme, which a browser reads as the start of the
authority
+ ("https:\\\\some_netlock.com", False),
+ ("https:/\\some_netlock.com", False),
+ ("https:\\/some_netlock.com", False),
+ ("https%3A%5C%5Csome_netlock.com", False),
+ # a single leading \ still resolves to a same-origin path
+ ("\\some_page", True),
+ ("/some_page", True),
],
)
def test_is_safe_url_without_prefix(self, url, expected_is_safe):