This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-2-test by this push:
new 5a5e7fb5a16 [v3-2-test] Fix log server path extraction to use
removeprefix (#66749) (#66772)
5a5e7fb5a16 is described below
commit 5a5e7fb5a165180c507522d1ebce47a8366295e9
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri May 15 18:23:00 2026 +0530
[v3-2-test] Fix log server path extraction to use removeprefix (#66749)
(#66772)
The log server uses request.url.path.lstrip("/log/") to extract the
requested filename from the URL path. str.lstrip() strips any
combination of the argument characters (here {/, l, o, g}) from the
left of the string -- it does not remove the literal prefix "/log/".
This is a documented Python pitfall.
Switch to str.removeprefix("/log/") (Python 3.9+, already required by
Airflow) so the filename extracted for JWT validation matches the one
the underlying Starlette StaticFiles mount uses to locate the file on
disk.
(cherry picked from commit d8865dd42499d1ded98640ddf9d88f37a8209904)
Generated-by: Claude Opus 4.7 (1M context) following the guidelines at
https:
//github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions
Co-authored-by: Jarek Potiuk <[email protected]>
---
airflow-core/src/airflow/utils/serve_logs/log_server.py | 2 +-
airflow-core/tests/unit/utils/test_serve_logs.py | 13 +++++++++++++
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/airflow-core/src/airflow/utils/serve_logs/log_server.py
b/airflow-core/src/airflow/utils/serve_logs/log_server.py
index 292d7bf5b16..92178bf5a24 100644
--- a/airflow-core/src/airflow/utils/serve_logs/log_server.py
+++ b/airflow-core/src/airflow/utils/serve_logs/log_server.py
@@ -67,7 +67,7 @@ class JWTAuthStaticFiles(StaticFiles):
payload = await signer.avalidated_claims(auth)
token_filename = payload.get("filename")
# Extract filename from url path
- request_filename = request.url.path.lstrip("/log/")
+ request_filename = request.url.path.removeprefix("/log/")
if token_filename is None:
logger.warning("The payload does not contain 'filename' key:
%s.", payload)
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
diff --git a/airflow-core/tests/unit/utils/test_serve_logs.py
b/airflow-core/tests/unit/utils/test_serve_logs.py
index b6c7805386a..ebd8d602eb4 100644
--- a/airflow-core/tests/unit/utils/test_serve_logs.py
+++ b/airflow-core/tests/unit/utils/test_serve_logs.py
@@ -124,6 +124,19 @@ class TestServeLogs:
)
assert response.status_code == 403
+ def test_forbidden_lstrip_character_overlap(self, client: TestClient,
jwt_generator):
+ # The request path and the JWT filename intersect on the set {/, l, o,
g}:
+ # str.lstrip("/log/") on "/log/log_sample.log" returns "_sample.log",
+ # which would have matched the JWT, but StaticFiles serves
"log_sample.log".
+ # removeprefix preserves the literal prefix so the two paths agree.
+ response = client.get(
+ "/log/log_sample.log",
+ headers={
+ "Authorization": jwt_generator.generate({"filename":
"_sample.log"}),
+ },
+ )
+ assert response.status_code == 403
+
def test_forbidden_expired(self, client: TestClient, jwt_generator):
with time_machine.travel("2010-01-14"):
token = jwt_generator.generate({"filename": "sample.log"})