This is an automated email from the ASF dual-hosted git repository.
vincbeck 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 1100afbec5c Reserve /auth and /pluginsv2 from plugin url_prefix
(#66501)
1100afbec5c is described below
commit 1100afbec5c4daa60670ebd9ba47ded55c14dd88
Author: Jarek Potiuk <[email protected]>
AuthorDate: Thu May 7 16:07:24 2026 +0200
Reserve /auth and /pluginsv2 from plugin url_prefix (#66501)
The framework mounts the auth-manager subapp under /auth and the FAB
plugin shim under /pluginsv2, but RESERVED_URL_PREFIXES only listed
/api/v2, /ui, and /execution. A trusted plugin attempting to mount under
either of the missing prefixes was accepted and (because plugin init runs
before the auth-manager mount) would shadow the auth routes.
Plugins are trusted code per Airflow's security model so this is
defense-in-depth, not a vulnerability — but accidental collisions with
the auth-manager / Flask-plugins mount points should be caught and
logged like the other reserved prefixes.
---
airflow-core/src/airflow/api_fastapi/app.py | 2 +-
airflow-core/tests/unit/api_fastapi/test_app.py | 19 ++++++-------------
2 files changed, 7 insertions(+), 14 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/app.py
b/airflow-core/src/airflow/api_fastapi/app.py
index 86ba79f7a06..8931840c880 100644
--- a/airflow-core/src/airflow/api_fastapi/app.py
+++ b/airflow-core/src/airflow/api_fastapi/app.py
@@ -61,7 +61,7 @@ def get_cookie_path() -> str:
# Fast API apps mounted under these prefixes are not allowed
-RESERVED_URL_PREFIXES = ["/api/v2", "/ui", "/execution"]
+RESERVED_URL_PREFIXES = ["/api/v2", "/ui", "/execution", "/auth", "/pluginsv2"]
log = logging.getLogger(__name__)
diff --git a/airflow-core/tests/unit/api_fastapi/test_app.py
b/airflow-core/tests/unit/api_fastapi/test_app.py
index 130247f60c0..1e8817ef243 100644
--- a/airflow-core/tests/unit/api_fastapi/test_app.py
+++ b/airflow-core/tests/unit/api_fastapi/test_app.py
@@ -98,27 +98,20 @@ def test_catch_all_route_last(client):
@pytest.mark.parametrize(
- ("fastapi_apps", "expected_message", "invalid_path"),
+ ("invalid_prefix", "expected_message"),
[
- (
- [{"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)),
- ),
+ ("", "'url_prefix' key is empty string for the fastapi app: test"),
+ *((prefix, "attempted to use reserved url_prefix") for prefix in
app_module.RESERVED_URL_PREFIXES),
],
)
-def test_plugin_with_invalid_url_prefix(caplog, fastapi_apps,
expected_message, invalid_path):
+def test_plugin_with_invalid_url_prefix(caplog, invalid_prefix,
expected_message):
+ fastapi_apps = [{"name": "test", "app": FastAPI(), "url_prefix":
invalid_prefix}]
app = FastAPI()
with mock.patch.object(plugins_manager, "get_fastapi_plugins",
return_value=(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)
+ assert not any(r.path == invalid_prefix for r in app.routes)
class TestGetCookiePath: