CVE-2026-41017
Description
Apache Airflow's JWTRefreshMiddleware omits the Secure flag on JWT cookies when behind a TLS-terminating proxy, allowing session hijacking via MITM.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache Airflow's JWTRefreshMiddleware omits the Secure flag on JWT cookies when behind a TLS-terminating proxy, allowing session hijacking via MITM.
Vulnerability
The vulnerability resides in Apache Airflow's JWTRefreshMiddleware, which sets the JWT authentication cookie without the Secure flag when the Airflow API server is deployed behind a TLS-terminating reverse proxy (e.g., nginx, Envoy, or a managed load balancer). The middleware derived the Secure flag solely from the local api.ssl_cert configuration, ignoring whether the incoming request was received over HTTPS. This affects deployments where TLS is terminated at the proxy and the Airflow process receives plaintext HTTP. All versions prior to apache-airflow 3.2.2 are affected.
Exploitation
An attacker with network position (Wi-Fi MITM, hostile LAN, or captive-portal proxy) can induce a logged-in user's browser to send an HTTP request to the deployment's hostname. Because the JWT cookie lacks the Secure flag, the browser includes it in cleartext HTTP requests. The attacker captures the cookie from that request and can replay it against the authenticated Airflow API.
Impact
Successful exploitation allows the attacker to impersonate the victim user, gaining full access to the Airflow API with the victim's privileges. This can lead to unauthorized disclosure of sensitive data, modification of workflows, or other actions depending on the user's permissions.
Mitigation
Upgrade to apache-airflow version 3.2.2 or later, which fixes the issue by setting the Secure flag when the request is HTTPS or a local SSL cert is configured [1]. No workaround is available for deployments that cannot upgrade immediately; administrators should ensure that the Airflow API server is not reachable over cleartext HTTP or that the reverse proxy sets the Secure flag on cookies.
AI Insight generated on Jun 1, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
260db83fa8023Set JWT refresh cookie Secure flag when request is HTTPS (#65348)
2 files changed · +46 −1
airflow-core/src/airflow/api_fastapi/auth/middlewares/refresh_token.py+1 −1 modified@@ -62,7 +62,7 @@ async def dispatch(self, request: Request, call_next): if new_token is not None: cookie_path = get_cookie_path() - secure = bool(conf.get("api", "ssl_cert", fallback="")) + secure = request.base_url.scheme == "https" or bool(conf.get("api", "ssl_cert", fallback="")) response.set_cookie( COOKIE_NAME_JWT_TOKEN, new_token,
airflow-core/tests/unit/api_fastapi/auth/middlewares/test_refresh_token.py+45 −0 modified@@ -131,6 +131,51 @@ async def test_dispatch_with_refreshed_user( set_cookie_headers = response.headers.get("set-cookie", "") assert f"{COOKIE_NAME_JWT_TOKEN}=new_token" in set_cookie_headers + @pytest.mark.parametrize( + ("scheme", "ssl_cert", "expected_secure"), + [ + pytest.param("https", "", True, id="https-no-local-ssl-cert"), + pytest.param("http", "/etc/ssl/cert.pem", True, id="http-with-local-ssl-cert"), + pytest.param("https", "/etc/ssl/cert.pem", True, id="https-with-local-ssl-cert"), + pytest.param("http", "", False, id="http-no-local-ssl-cert"), + ], + ) + @patch("airflow.api_fastapi.auth.middlewares.refresh_token.get_auth_manager") + @patch("airflow.api_fastapi.auth.middlewares.refresh_token.resolve_user_from_token") + @patch("airflow.api_fastapi.auth.middlewares.refresh_token.conf") + @pytest.mark.asyncio + async def test_dispatch_cookie_secure_flag( + self, + mock_conf, + mock_resolve_user_from_token, + mock_get_auth_manager, + middleware, + mock_request, + mock_user, + scheme, + ssl_cert, + expected_secure, + ): + """The cookie Secure flag must follow the request scheme as well as the local ssl_cert.""" + refreshed_user = MagicMock(spec=BaseUser) + mock_request.cookies = {COOKIE_NAME_JWT_TOKEN: "valid_token"} + mock_request.base_url = MagicMock(scheme=scheme) + mock_resolve_user_from_token.return_value = mock_user + mock_auth_manager = MagicMock() + mock_get_auth_manager.return_value = mock_auth_manager + mock_auth_manager.refresh_user.return_value = refreshed_user + mock_auth_manager.generate_jwt.return_value = "new_token" + mock_conf.get.return_value = ssl_cert + + call_next = AsyncMock(return_value=Response()) + response = await middleware.dispatch(mock_request, call_next) + + set_cookie_headers = response.headers.get("set-cookie", "") + if expected_secure: + assert "Secure" in set_cookie_headers + else: + assert "Secure" not in set_cookie_headers + @patch("airflow.api_fastapi.auth.middlewares.refresh_token.get_cookie_path", return_value="/team-a/") @patch("airflow.api_fastapi.auth.middlewares.refresh_token.get_auth_manager") @patch("airflow.api_fastapi.auth.middlewares.refresh_token.resolve_user_from_token")
cde4885818beUpdating release notes for 3.2.2rc3
2 files changed · +5 −4
RELEASE_NOTES.rst+3 −2 modified@@ -24,7 +24,7 @@ .. towncrier release notes start -Airflow 3.2.2 (2026-05-27) +Airflow 3.2.2 (2026-05-29) -------------------------- Significant Changes @@ -81,7 +81,8 @@ Significant Changes Bug Fixes ^^^^^^^^^ - +- Fix ``Callback.handle_event`` triggerer crash when OpenTelemetry metrics receive dict typed tag values (#67527) (#67529) +- UI: Rewrite ``modulepreload hrefs`` to the api-server static path (#67548) (#67556) - Correctly pre-allocate ``external_executor_id`` with multiple executors on PostgreSQL (#67388) (#67458) - Return raw import-error stacktrace when a Dag file has no registered Dag (#67465) (#67478) - UI: Fix Expand/Collapse All on XComs and Audit Log JSON cells (#67316) (#67361)
reproducible_build.yaml+2 −2 modified@@ -1,2 +1,2 @@ -release-notes-hash: 6407b48d1054fe3ce68c09bf4435d91d -source-date-epoch: 1779745327 +release-notes-hash: 504288db9a9dc13a0db859232fab98d0 +source-date-epoch: 1779811737
Vulnerability mechanics
Root cause
"JWTRefreshMiddleware derived the cookie Secure flag only from the local ssl_cert config, ignoring the request scheme, so deployments behind TLS-terminating proxies received the JWT refresh cookie without the Secure flag."
Attack vector
A network-positioned attacker (Wi-Fi MITM, hostile LAN, captive-portal proxy) can induce a logged-in user's browser to issue an HTTP request to the deployment's hostname. Because the JWT refresh cookie lacks the `Secure` flag, the browser sends it over cleartext HTTP, allowing the attacker to capture the cookie and replay it against the authenticated API [CWE-614]. This affects deployments where the Airflow API server is reached through a TLS-terminating reverse proxy and the cookie's secure-by-default protection is load-bearing for session integrity [ref_id=1].
Affected code
The `JWTRefreshMiddleware` in `airflow/api_fastapi/auth/middlewares/refresh_token.py` derived the cookie `Secure` flag solely from the local `api.ssl_cert` configuration. Deployments where TLS is terminated at a reverse proxy (no local SSL cert on the Airflow process) therefore received the JWT refresh cookie without the `Secure` flag [patch_id=4186041].
What the fix does
The patch changes the `secure` variable assignment in `refresh_token.py` from `bool(conf.get("api", "ssl_cert", fallback=""))` to `request.base_url.scheme == "https" or bool(conf.get("api", "ssl_cert", fallback=""))` [patch_id=4186041]. This matches the pattern already used by every other cookie-setting location in the codebase: the `Secure` flag is now set when either the request arrived over HTTPS or a local SSL certificate is configured, closing the gap for proxy-terminated TLS deployments [ref_id=1].
Preconditions
- configAirflow API server deployed behind an HTTPS-terminating reverse proxy (e.g., nginx, Envoy, managed load balancer) with no local ssl_cert configured on the Airflow process.
- networkAttacker must be in a network position to intercept cleartext HTTP traffic (Wi-Fi MITM, hostile LAN, captive-portal proxy).
- authVictim must be logged into the Airflow API server and have an active JWT refresh cookie.
- inputAttacker must induce the victim's browser to send an HTTP request to the deployment's hostname.
Generated on Jun 1, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.