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

vatsrahul1001 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 d8865dd4249 Fix log server path extraction to use removeprefix (#66749)
d8865dd4249 is described below

commit d8865dd42499d1ded98640ddf9d88f37a8209904
Author: Jarek Potiuk <[email protected]>
AuthorDate: Tue May 12 16:43:17 2026 +0200

    Fix log server path extraction to use removeprefix (#66749)
    
    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.
    
    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
---
 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"})

Reply via email to