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 555f35576e3 Updates the is_url_safe method to reject urls with ///
(#65557)
555f35576e3 is described below
commit 555f35576e3143dec65a7fb9acd95891841b45f2
Author: Aritra Basu <[email protected]>
AuthorDate: Thu Apr 23 22:55:45 2026 +0530
Updates the is_url_safe method to reject urls with /// (#65557)
* Updates the is_url_safe method to reject urls with ///
JS's URL constructor sticks to WHATWG's Url parsing for
special schemes ref: https://url.spec.whatwg.org/#url-parsing
Due to which a host like ///example.com is a valid url in JS
and resolves to http(s)://example.com if the base url is a http(s)
URL.
The same thing happens with \.
* Adds unit tests for is_safe_url
---
airflow-core/src/airflow/api_fastapi/core_api/security.py | 6 ++++++
.../tests/unit/api_fastapi/core_api/test_security.py | 15 +++++++++++++++
2 files changed, 21 insertions(+)
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 774cfa9ed6a..941a4c418f5 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/security.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/security.py
@@ -779,6 +779,12 @@ def is_safe_url(target_url: str, request: Request | None =
None) -> bool:
# Can't enforce any security check.
return True
+ # 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(("//", "/\\", "\\/", "\\\\")):
+ return False
for base_url, parsed_base in parsed_bases:
parsed_target = urlparse(urljoin(base_url, unquote(target_url))) #
Resolves relative URLs
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 e5cb5f17cf9..9b8a3abd762 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
@@ -413,6 +413,21 @@ class TestFastApiSecurity:
request.base_url = "https://requesting_server_base_url.com/prefix2"
assert is_safe_url(url, request=request) == expected_is_safe
+ @pytest.mark.parametrize(
+ ("url", "expected_is_safe"),
+ [
+ # Using \ or /// to escape host check
+ ("///some_netlock.com/prefix", False),
+ ("\\\\some_netlock.com/prefix", False),
+ # encoded url
+ ("%5C%5C%5C%5Csome_netlock.com/prefix", False),
+ ],
+ )
+ def test_is_safe_url_without_prefix(self, url, expected_is_safe):
+ request = Mock()
+ request.base_url = "https://requesting_server_base_url.com/"
+ assert is_safe_url(url, request=request) == expected_is_safe
+
@pytest.mark.parametrize(
("url", "expected_is_safe"),
[