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

potiuk 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 b94f36f2d01 Add tests for ti:self JWT scope enforcement on execution 
API (#66071)
b94f36f2d01 is described below

commit b94f36f2d019ea60dd4f5a91494d3139b4e5dbee
Author: Amogh Desai <[email protected]>
AuthorDate: Sun May 10 07:47:13 2026 +0530

    Add tests for ti:self JWT scope enforcement on execution API (#66071)
    
    * Add tests for ti:self JWT scope enforcement on Execution API
    
    The ti:self scope check at security.py rejects requests where the
    JWT subject does not match the task_instance_id in the path. This is
    the cross-task isolation guarantee for any router that opts into
    ti:self (e.g. /task-instances, /hitl, and the upcoming task state
    endpoints from AIP-103). Until now the check was uncovered.
    
    Adds three tests mirroring the existing TestTokenTypeScopeEnforcement
    pattern: matching subject is accepted, mismatched subject returns 403,
    and routes without ti:self ignore subject mismatches.
    
    * update
---
 .../api_fastapi/execution_api/test_security.py     | 57 ++++++++++++++++++++++
 1 file changed, 57 insertions(+)

diff --git a/airflow-core/tests/unit/api_fastapi/execution_api/test_security.py 
b/airflow-core/tests/unit/api_fastapi/execution_api/test_security.py
index 985dfd32599..22b7f58a959 100644
--- a/airflow-core/tests/unit/api_fastapi/execution_api/test_security.py
+++ b/airflow-core/tests/unit/api_fastapi/execution_api/test_security.py
@@ -158,6 +158,63 @@ class TestTokenTypeScopeEnforcement:
         assert run.status_code == 200
 
 
+class TestTiSelfScopeEnforcement:
+    """Routes with the ``ti:self`` scope reject mismatched JWT subjects."""
+
+    PATH_TI_ID = "00000000-0000-0000-0000-000000000001"
+    OTHER_TI_ID = "00000000-0000-0000-0000-000000000002"
+
+    @pytest.fixture
+    def app(self):
+        """One router enforces ti:self, another doesn't — to confirm 
enforcement is opt-in."""
+        app = FastAPI()
+
+        authenticated_router = APIRouter(dependencies=[Security(require_auth)])
+        ti_self_router = APIRouter(dependencies=[Security(require_auth, 
scopes=["ti:self"])])
+
+        @ti_self_router.get("/{task_instance_id}/state")
+        def state_endpoint(task_instance_id: str):
+            return {"ok": True}
+
+        @authenticated_router.get("/no-scope/{task_instance_id}")
+        def no_scope_endpoint(task_instance_id: str):
+            return {"ok": True}
+
+        authenticated_router.include_router(ti_self_router, prefix="/ti")
+        app.include_router(authenticated_router)
+        return app
+
+    def _override_jwt(self, app: FastAPI, token_ti_id: UUID):
+        async def mock_jwt(request: Request):
+            return TIToken(id=token_ti_id, claims=TIClaims(scope="execution"))
+
+        app.dependency_overrides[_jwt_bearer] = mock_jwt
+
+    def test_matching_subject_is_accepted(self, app):
+        self._override_jwt(app, self.PATH_TI_ID)
+        client = TestClient(app)
+
+        resp = client.get(
+            f"/ti/{self.PATH_TI_ID}/state",
+            headers={"Authorization": "Bearer fake"},
+        )
+
+        assert resp.status_code == 200
+
+    def test_mismatched_subject_is_rejected(self, app):
+        """A task cannot read or write another task's resources."""
+        self._override_jwt(app, self.OTHER_TI_ID)
+        client = TestClient(app)
+
+        resp = client.get(
+            f"/ti/{self.PATH_TI_ID}/state",
+            headers={"Authorization": "Bearer fake"},
+        )
+
+        assert resp.status_code == 403
+        assert "does not match" in resp.json()["detail"]
+
+
 class TestGetTeamNameDep:
     """Tests for get_team_name_dep avoiding unnecessary async sessions."""
 

Reply via email to