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

amoghrajesh 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 91cce6484ce Escape URL for DagOperations lookup in task sdk client 
(#68129)
91cce6484ce is described below

commit 91cce6484cee216e16cae77fd14c33dfad7398e8
Author: GPK <[email protected]>
AuthorDate: Mon Jun 15 12:09:02 2026 +0100

    Escape URL for DagOperations lookup in task sdk client (#68129)
---
 task-sdk/src/airflow/sdk/api/client.py     |  3 ++-
 task-sdk/tests/task_sdk/api/test_client.py | 31 ++++++++++++++++++++++++++++++
 2 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/task-sdk/src/airflow/sdk/api/client.py 
b/task-sdk/src/airflow/sdk/api/client.py
index 824b0d27dc7..66496d5cac9 100644
--- a/task-sdk/src/airflow/sdk/api/client.py
+++ b/task-sdk/src/airflow/sdk/api/client.py
@@ -25,6 +25,7 @@ from datetime import datetime
 from functools import cache
 from http import HTTPStatus
 from typing import TYPE_CHECKING, Any, TypeVar
+from urllib.parse import quote
 
 import certifi
 import httpx
@@ -989,7 +990,7 @@ class DagsOperations:
 
     def get(self, dag_id: str) -> DagResponse:
         """Get a DAG via the API server."""
-        resp = self.client.get(f"dags/{dag_id}")
+        resp = self.client.get(f"dags/{quote(dag_id, safe='')}")
         return DagResponse.model_validate_json(resp.read())
 
 
diff --git a/task-sdk/tests/task_sdk/api/test_client.py 
b/task-sdk/tests/task_sdk/api/test_client.py
index 2aeb0fc298a..0d6ee44628a 100644
--- a/task-sdk/tests/task_sdk/api/test_client.py
+++ b/task-sdk/tests/task_sdk/api/test_client.py
@@ -1737,6 +1737,37 @@ class TestDagsOperations:
             next_dagrun=datetime(2026, 4, 13, tzinfo=dt_timezone.utc),
         )
 
+    def test_get_url_quotes_dag_id_as_single_path_segment(self):
+        """Test that Dag IDs cannot escape the dags API path."""
+        requests_seen = []
+        crafted_dag_id = "x/../../variables/secret_key"
+
+        def handle_request(request: httpx.Request) -> httpx.Response:
+            requests_seen.append(request)
+            if request.url.path == "/variables/secret_key":
+                return httpx.Response(
+                    status_code=200,
+                    json={"key": "secret_key", "value": "super-secret-value"},
+                )
+            return httpx.Response(
+                status_code=404,
+                json={
+                    "detail": {
+                        "message": "The Dag was not found",
+                        "reason": "not_found",
+                    }
+                },
+            )
+
+        client = make_client(transport=httpx.MockTransport(handle_request))
+
+        with pytest.raises(ServerResponseError) as exc_info:
+            client.dags.get(dag_id=crafted_dag_id)
+
+        assert requests_seen[0].url.raw_path == 
b"/dags/x%2F..%2F..%2Fvariables%2Fsecret_key"
+        assert requests_seen[0].url.path != "/variables/secret_key"
+        assert "super-secret-value" not in str(exc_info.value)
+
     def test_get_not_found(self):
         """Test that getting a missing dag raises a server response error."""
 

Reply via email to