This is an automated email from the ASF dual-hosted git repository.

potiuk 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 684ec8413c2 Do not allow out-of-base-path redirection (#62430)
684ec8413c2 is described below

commit 684ec8413c269d15a639475322c2eb806aca60bf
Author: Jarek Potiuk <[email protected]>
AuthorDate: Wed Feb 25 07:07:21 2026 +0100

    Do not allow out-of-base-path redirection (#62430)
    
    Co-authored-by: Copilot Autofix powered by AI 
<62310815+github-advanced-security[bot]@users.noreply.github.com>
---
 airflow-core/src/airflow/api_fastapi/core_api/security.py | 15 +++++++++++----
 1 file changed, 11 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 fc6b398fe21..6acbb82494d 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/security.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/security.py
@@ -16,10 +16,10 @@
 # under the License.
 from __future__ import annotations
 
+import posixpath
 from collections.abc import Callable, Coroutine
 from contextlib import suppress
 from json import JSONDecodeError
-from pathlib import Path
 from typing import TYPE_CHECKING, Annotated, Any, cast
 from urllib.parse import ParseResult, unquote, urljoin, urlparse
 
@@ -734,10 +734,17 @@ def is_safe_url(target_url: str, request: Request | None 
= None) -> bool:
     for base_url, parsed_base in parsed_bases:
         parsed_target = urlparse(urljoin(base_url, unquote(target_url)))  # 
Resolves relative URLs
 
-        target_path = Path(parsed_target.path).resolve()
+        base_path = parsed_base.path or "/"
+        target_path = parsed_target.path or "/"
 
-        if target_path and parsed_base.path and not 
target_path.is_relative_to(parsed_base.path):
-            continue
+        # Normalize as POSIX paths (URL paths) and ensure target is under base.
+        norm_base = posixpath.normpath(base_path)
+        norm_target = posixpath.normpath(target_path)
+
+        if norm_base != "/":
+            norm_base_with_slash = norm_base if norm_base.endswith("/") else 
norm_base + "/"
+            if norm_target != norm_base and not 
norm_target.startswith(norm_base_with_slash):
+                continue
 
         if parsed_target.scheme in {"http", "https"} and parsed_target.netloc 
== parsed_base.netloc:
             return True

Reply via email to