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

kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 5ab7ae61dc52575f0ee0c7a938d058a3457a87cc
Author: anasatzemoso <[email protected]>
AuthorDate: Wed Sep 24 19:01:48 2025 +0530

    fix: Gracefully handle fastAPI plugins with empty url_prefix (#55262)
    
    * fix: Gracefully handle fastAPI plugins with empty url_prefix
    
    * fix: log empty url_prefix and return all plugins in get_plugin_info
    
    * Validate plugin url_prefix against reserved routes
    
    * fix: Gracefully handle fastAPI plugins with empty url_prefix
    
    * fix: log empty url_prefix and return all plugins in get_plugin_info
    
    * Validate plugin url_prefix against reserved routes
    
    * Testcases for checking empty and reserved url_prefix
    
    * fix: Validate url_prefix using startswith
    
    * Simplified the for loop
    
    Co-authored-by: LIU ZHE YOU <[email protected]>
    
    * Converted set to list data structure for RESERVED_URL_PREFIXES
    
    Co-authored-by: LIU ZHE YOU <[email protected]>
    
    * Refactoring the test cases
    
    * fix: CI issue
    
    ---------
    
    Co-authored-by: LIU ZHE YOU <[email protected]>
    (cherry picked from commit 757d9ae3fb7e8d82ff544e434743abdaece51daa)
---
 airflow-core/src/airflow/api_fastapi/app.py     |  9 ++++++++
 airflow-core/tests/unit/api_fastapi/test_app.py | 28 +++++++++++++++++++++++++
 2 files changed, 37 insertions(+)

diff --git a/airflow-core/src/airflow/api_fastapi/app.py 
b/airflow-core/src/airflow/api_fastapi/app.py
index bae201c464e..3e76c4f8f2d 100644
--- a/airflow-core/src/airflow/api_fastapi/app.py
+++ b/airflow-core/src/airflow/api_fastapi/app.py
@@ -48,6 +48,9 @@ API_ROOT_PATH = urlsplit(API_BASE_URL).path
 # Define the full path on which the potential auth manager fastapi is mounted
 AUTH_MANAGER_FASTAPI_APP_PREFIX = f"{API_ROOT_PATH}auth"
 
+# Fast API apps mounted under these prefixes are not allowed
+RESERVED_URL_PREFIXES = ["/api/v2", "/ui", "/execution"]
+
 log = logging.getLogger(__name__)
 
 app: FastAPI | None = None
@@ -183,6 +186,12 @@ def init_plugins(app: FastAPI) -> None:
         if url_prefix is None:
             log.error("'url_prefix' key is missing for the fastapi app: %s", 
name)
             continue
+        if url_prefix == "":
+            log.error("'url_prefix' key is empty string for the fastapi app: 
%s", name)
+            continue
+        if any(url_prefix.startswith(prefix) for prefix in 
RESERVED_URL_PREFIXES):
+            log.error("Plugin %s attempted to use reserved url_prefix '%s'", 
name, url_prefix)
+            continue
 
         log.debug("Adding subapplication %s under prefix %s", name, url_prefix)
         app.mount(url_prefix, subapp)
diff --git a/airflow-core/tests/unit/api_fastapi/test_app.py 
b/airflow-core/tests/unit/api_fastapi/test_app.py
index d708aae2edf..448d527ab6b 100644
--- a/airflow-core/tests/unit/api_fastapi/test_app.py
+++ b/airflow-core/tests/unit/api_fastapi/test_app.py
@@ -19,6 +19,10 @@ from __future__ import annotations
 from unittest import mock
 
 import pytest
+from fastapi import FastAPI
+
+import airflow.api_fastapi.app as app_module
+import airflow.plugins_manager as plugins_manager
 
 pytestmark = pytest.mark.db_test
 
@@ -90,3 +94,27 @@ def test_catch_all_route_last(client):
     """
     test_app = client(apps="all").app
     assert test_app.routes[-1].path == "/{rest_of_path:path}"
+
+
[email protected](
+    "fastapi_apps, expected_message, invalid_path",
+    [
+        (
+            [{"name": "test", "app": FastAPI(), "url_prefix": ""}],
+            "'url_prefix' key is empty string for the fastapi app: test",
+            "",
+        ),
+        (
+            [{"name": "test", "app": FastAPI(), "url_prefix": 
next(iter(app_module.RESERVED_URL_PREFIXES))}],
+            "attempted to use reserved url_prefix",
+            next(iter(app_module.RESERVED_URL_PREFIXES)),
+        ),
+    ],
+)
+def test_plugin_with_invalid_url_prefix(caplog, fastapi_apps, 
expected_message, invalid_path):
+    app = FastAPI()
+    with mock.patch.object(plugins_manager, "fastapi_apps", fastapi_apps):
+        app_module.init_plugins(app)
+
+    assert any(expected_message in rec.message for rec in caplog.records)
+    assert not any(r.path == invalid_path for r in app.routes)

Reply via email to