Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions airflow-core/newsfragments/67868.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The ``kwargs`` field of trigger objects returned by the REST API (for example in the ``trigger`` of a task-instance response) no longer exposes the decrypted trigger keyword arguments. Those kwargs can contain credentials a deferred operator hands to its trigger (an API key, a token, …), so the field is now always returned empty, as ``"{}"``. The field is retained in the response schema for backwards compatibility, and the triggerer still decrypts and uses the real kwargs at runtime — only the API representation is emptied.
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,29 @@
from airflow.api_fastapi.core_api.base import BaseModel


def _redact_kwargs(_: object) -> str:
"""
Return empty trigger kwargs for API responses.

Trigger ``kwargs`` may contain sensitive values (for example credentials a deferred
operator hands to its trigger -- an API key, a token), so they are never exposed through
the REST API. The field is kept in the response schema for backwards compatibility -- so
existing API consumers do not break on a missing property -- but it is always returned
empty, as ``"{}"`` (the stringified empty dict, matching the string format the field has
always used). The triggerer still decrypts and uses the real kwargs at runtime; only the
API representation is emptied.
"""
return "{}"


class TriggerResponse(BaseModel):
"""Trigger serializer for responses."""

model_config = ConfigDict(populate_by_name=True)

id: int
classpath: str
kwargs: Annotated[str, BeforeValidator(str)]
kwargs: Annotated[str, BeforeValidator(_redact_kwargs)]
created_date: datetime
queue: str | None
triggerer_id: int | None
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations

from datetime import datetime

import pytest

from airflow.api_fastapi.core_api.datamodels.trigger import TriggerResponse


class _Trigger:
"""Stand-in for the ``Trigger`` ORM object a ``TriggerResponse`` is built from."""

id = 1
classpath = "airflow.providers.standard.triggers.temporal.DateTimeTrigger"
created_date = datetime(2024, 1, 1)
queue = None
triggerer_id = None

def __init__(self, kwargs):
self.kwargs = kwargs


class TestTriggerResponse:
@pytest.mark.parametrize(
"kwargs",
[
pytest.param({"api_key": "super-secret", "polling_interval": 30}, id="sensitive-values"),
pytest.param({}, id="already-empty"),
],
)
def test_kwargs_are_always_redacted_to_empty(self, kwargs):
"""Trigger kwargs may hold credentials, so the API always returns them empty as ``"{}"``."""
response = TriggerResponse.model_validate(_Trigger(kwargs), from_attributes=True)

assert response.kwargs == "{}"
# The schema must remain a string for backwards compatibility.
assert isinstance(response.kwargs, str)
Loading