diff --git a/providers/common/ai/src/airflow/providers/common/ai/mixins/approval.py b/providers/common/ai/src/airflow/providers/common/ai/mixins/approval.py index 22fa641333b52..df5929eedc0a6 100644 --- a/providers/common/ai/src/airflow/providers/common/ai/mixins/approval.py +++ b/providers/common/ai/src/airflow/providers/common/ai/mixins/approval.py @@ -168,8 +168,10 @@ def execute_complete(self, context: Context, generated_output: str, event: dict[ output = generated_output params_input: dict[str, Any] = event.get("params_input") or {} - # If the reviewer provided modified output, return their version - if params_input: + # Only accept modified output when the operator explicitly allows modifications. + # Without this guard a reviewer could craft a request with params_input even + # when allow_modifications=False, bypassing the read-only approval flow. + if getattr(self, "allow_modifications", False) and params_input: modified = params_input.get("output") if modified is not None and modified != generated_output: log.info("output=%s modified by the reviewer=%s ", modified, responded_by_user) diff --git a/providers/common/ai/tests/unit/common/ai/mixins/test_approval.py b/providers/common/ai/tests/unit/common/ai/mixins/test_approval.py index 7b2d2b90f89b5..08355c3b92237 100644 --- a/providers/common/ai/tests/unit/common/ai/mixins/test_approval.py +++ b/providers/common/ai/tests/unit/common/ai/mixins/test_approval.py @@ -295,6 +295,18 @@ def test_approved_no_modifications_ignores_params_input(self, approval_op): assert result == "original" + def test_approved_no_modifications_rejects_tampered_params_input(self, approval_op): + """When allow_modifications=False, tampered params_input with output must be ignored.""" + event = { + "chosen_options": ["Approve"], + "responded_by_user": "reviewer", + "params_input": {"output": "tampered output"}, + } + + result = approval_op.execute_complete({}, generated_output="original", event=event) + + assert result == "original" + def test_event_missing_responded_by_user(self, approval_op): event = {"chosen_options": ["Approve"]}