potiuk commented on code in PR #70890:
URL: https://github.com/apache/airflow/pull/70890#discussion_r3708255175


##########
airflow-core/src/airflow/api_fastapi/logging/decorators.py:
##########


Review Comment:
   Good catch, and it is worth noting it is newly reachable rather than merely 
pre-existing: before this change a bulk body never got as far as the `extra` 
branch, because the only top-level key it presented was `actions`. Now it does.
   
   Widened the except to `(json.JSONDecodeError, TypeError)`. Added 
`test_non_string_extra_does_not_raise_and_does_not_leak`, parametrized over an 
already-decoded object, an already-decoded array, a number and a bool — all 
four raise `TypeError` out of the audit-log decorator without the fix, and all 
four assert the secret does not survive into the serialized entry with it.
   
   ---
   Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting
   



##########
airflow-core/tests/unit/api_fastapi/logging/test_decorators.py:
##########
@@ -130,6 +130,141 @@ def test_value_without_key_is_still_masked(self):
         assert result == {"value": "***"}
 
 
+class TestMaskBulkFields:
+    """The bulk endpoints nest their entities, and the masking has to reach 
them.
+
+    A ``BulkBody`` has exactly one top-level field, ``actions``; the entities 
carrying the
+    secrets sit two levels down, in ``actions[].entities[]``. Both maskers 
dispatch on
+    top-level key names, so a bulk body previously presented them with the 
single key
+    ``actions`` -- neither ``val``/``value`` for variables, nor ``extra`` for 
connections --
+    and the payload was written to the audit log as supplied.
+    """

Review Comment:
   Right — the class docstring was restating `_mask_bulk_entities`'s docstring 
almost sentence for sentence. Cut down to a single line; the mechanism stays 
documented at the helper.
   
   ---
   Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting
   



##########
airflow-core/src/airflow/api_fastapi/logging/decorators.py:
##########
@@ -45,8 +45,51 @@ def _sanitize_for_stdlib_log(value: str) -> str:
     return value.replace("\r", " ").replace("\n", " ")
 
 
+def _mask_bulk_entities(extra_fields, mask_entity):
+    """
+    Apply per-entity masking to a bulk request body.
+
+    A ``BulkBody`` has exactly one top-level field, ``actions``; the entities 
carrying the
+    secrets sit two levels down, in ``actions[].entities[]``. The per-entity 
maskers below
+    inspect top-level key names, so handing them a bulk body means they see 
only the key
+    ``actions`` and pass its whole payload through untouched. Reach the 
entities first.
+
+    Returns ``None`` when the body is not bulk-shaped, so callers fall back to 
flat masking.
+    """
+    actions = extra_fields.get("actions")
+    if not isinstance(actions, list):
+        return None
+
+    masked_actions = []
+    for action in actions:
+        if not isinstance(action, dict):

Review Comment:
   Not adding this one, because `None` here does not mean "unrecognised shape" 
— it means "not a bulk body", which is the ordinary case.
   
   Every single-entity endpoint routed through this decorator sends a flat body 
with no `actions` key: `POST /connections`, `PATCH /connections/{id}`, `POST 
/variables`, `PATCH /variables/{key}` and so on. All of them take this return 
path on every request, and the caller then does exactly the right thing with it 
— falls back to flat masking. A warning here would fire on the majority of 
audited write requests and say nothing useful.
   
   The narrower case — a body that *is* bulk-shaped but malformed, say 
`actions` present but an entity list that is not a list — is a request FastAPI 
rejects with 422 immediately after this runs. Nothing is written to the audit 
log for it, so there is no unmasked payload to warn about.
   
   If the worry is a bulk shape changing under us and masking silently going 
quiet, the guard I would trust is a test rather than a log line, and 
`test_no_secret_survives_in_the_serialized_entry` is that test: it asserts on 
the serialized entry rather than on one field, so a shape change that stops 
reaching the entities fails it.
   
   ---
   Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to