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 3e3b44cd4fe UI: Block polling requests to endpoints that returned 403
Forbidden (#64333)
3e3b44cd4fe is described below
commit 3e3b44cd4fefba2beef91c7468f5dc678ab21f59
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Mon Mar 30 18:19:18 2026 +0200
UI: Block polling requests to endpoints that returned 403 Forbidden (#64333)
* UI: Block polling requests to endpoints that returned 403 Forbidden
Adds axios interceptors to track URLs that return 403 and abort
subsequent requests to those URLs. Permissions don't change mid-session,
so this prevents wasted polling traffic without requiring per-component
changes.
* UI: Only block polling for permission-based 403s, not auth-related ones
Narrow the 403 URL tracking to responses with detail "Forbidden"
(missing permissions). Auth-related 403s like "Invalid JWT token"
are left alone to trigger the login redirect.
---------
Co-authored-by: Rahul Vats <[email protected]>
---
airflow-core/src/airflow/ui/src/main.tsx | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/airflow-core/src/airflow/ui/src/main.tsx
b/airflow-core/src/airflow/ui/src/main.tsx
index 44d9934f75e..9fb55690e4c 100644
--- a/airflow-core/src/airflow/ui/src/main.tsx
+++ b/airflow-core/src/airflow/ui/src/main.tsx
@@ -48,6 +48,23 @@ Reflect.set(globalThis, "ReactRouterDOM", ReactRouterDOM);
Reflect.set(globalThis, "ChakraUI", ChakraUI);
Reflect.set(globalThis, "EmotionReact", EmotionReact);
+// URLs that returned 403 Forbidden. Permissions won't change mid-session,
+// so we block further requests to avoid spamming the server with polling.
+const forbidden403Urls = new Set<string>();
+
+// Block outgoing requests to URLs that previously returned 403.
+// The request is aborted immediately so no network traffic occurs.
+axios.interceptors.request.use((config) => {
+ if (config.url !== undefined && forbidden403Urls.has(config.url)) {
+ const controller = new AbortController();
+
+ controller.abort();
+ config.signal = controller.signal;
+ }
+
+ return config;
+});
+
// redirect to login page if the API responds with unauthorized or forbidden
errors
axios.interceptors.response.use(
(response) => response,
@@ -64,6 +81,16 @@ axios.interceptors.response.use(
globalThis.location.replace(`${loginPath}?${params.toString()}`);
}
+ // Track permission-based 403 URLs so future polling requests are blocked
at the request interceptor.
+ // Only block "Forbidden" (missing permissions), not other auth-related
403s.
+ if (
+ error.response?.status === 403 &&
+ error.response.data.detail === "Forbidden" &&
+ error.config?.url !== undefined
+ ) {
+ forbidden403Urls.add(error.config.url);
+ }
+
return Promise.reject(error);
},
);