CVE-2026-42358
Description
Apache Airflow's Variable response masker bypasses redaction of sensitive keys when JSON nesting depth exceeds the recursion limit, exposing plaintext secrets to authenticated users.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache Airflow's Variable response masker bypasses redaction of sensitive keys when JSON nesting depth exceeds the recursion limit, exposing plaintext secrets to authenticated users.
Vulnerability
A bug in SecretsMasker._redact in Apache Airflow causes the masker to short-circuit on depth > max_depth before checking whether the current key name is sensitive (e.g., password, token, secret, api_key). For JSON Variables with nesting depth exceeding the default recursion limit of 5, the original value is returned unchanged instead of being replaced with ***. This is a residual gap in the fix for CVE-2026-32690, which addressed shallower nesting but did not raise the depth cap. Affects deployments that store sensitive values inside deeply-nested JSON Variables in Apache Airflow versions prior to 3.2.2 [1].
Exploitation
An attacker needs authenticated UI or API access with Variable read permission. By retrieving a Variable containing a deeply-nested JSON object where a sensitive key appears at a depth greater than 5, the masker fails to redact the value, returning the plaintext secret. No additional privileges or user interaction beyond standard Variable read access are required [1].
Impact
Successful exploitation allows an attacker to harvest plaintext secret values (e.g., passwords, tokens, API keys) stored under sensitive keys in deeply-nested JSON Variables. This results in information disclosure of credentials that may enable further compromise of the Airflow environment or connected services. The attacker gains the secret values at the privilege level of a read-only Variable user [1].
Mitigation
The vulnerability is fixed in apache-airflow version 3.2.2, released around May 2026. Users who already upgraded for CVE-2026-32690 must additionally upgrade to 3.2.2 or later to cover the deep-nesting path. No workaround is documented; upgrading is the only mitigation. The CVE is not listed on the CISA Known Exploited Vulnerabilities catalog as of publication [1].
AI Insight generated on Jun 1, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
2354391bbccc1Check sensitive key names before applying recursion-depth cutoff in secrets masker (#65912)
2 files changed · +51 −5
shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py+13 −5 modified@@ -344,14 +344,18 @@ def _redact_all( def _redact( self, item: Redactable, name: str | None, depth: int, max_depth: int, replacement: str = "***" ) -> Redacted: - # Avoid spending too much effort on redacting on deeply nested - # structures. This also avoid infinite recursion if a structure has - # reference to self. - if depth > max_depth: - return item try: + # Key-name-based redaction is unbounded by depth — sensitive keys + # must fail closed at any nesting level. The depth cutoff below is + # only used to bound the work of pattern-based string masking and + # to terminate recursion through self-referential iterables. if name and self.should_hide_value_for_key(name): return self._redact_all(item, depth, max_depth, replacement=replacement) + # Always walk dicts so deeper sensitive keys are still caught; + # JSON-loaded payloads cannot be self-referential, and any + # in-memory cycle hits Python's own recursion limit and is caught + # by the except clause below (which fails closed via + # "<redaction-failed>"). if isinstance(item, dict): to_return = { dict_key: self._redact( @@ -360,6 +364,10 @@ def _redact( for dict_key, subval in item.items() } return to_return + # Avoid spending too much effort on pattern-based masking of + # deeply nested non-dict structures. + if depth > max_depth: + return item if isinstance(item, Enum): return self._redact( item=item.value, name=name, depth=depth, max_depth=max_depth, replacement=replacement
shared/secrets_masker/tests/secrets_masker/test_secrets_masker.py+38 −0 modified@@ -690,6 +690,44 @@ def test_redact_max_depth(self, val, expected, max_depth): got = redact(val, max_depth=max_depth) assert got == expected + @pytest.mark.parametrize( + ("val", "expected"), + [ + # Sensitive key at exactly MAX_RECURSION_DEPTH (5) is redacted. + ( + {"a": {"b": {"c": {"d": {"password": "leaked"}}}}}, + {"a": {"b": {"c": {"d": {"password": "***"}}}}}, + ), + # Sensitive key one level past MAX_RECURSION_DEPTH is also redacted. + ( + {"a": {"b": {"c": {"d": {"e": {"password": "leaked"}}}}}}, + {"a": {"b": {"c": {"d": {"e": {"password": "***"}}}}}}, + ), + # Two levels past MAX_RECURSION_DEPTH, under a non-sensitive + # intermediate key, still fails closed. + ( + {"a": {"b": {"c": {"d": {"e": {"f": {"token": "leaked"}}}}}}}, + {"a": {"b": {"c": {"d": {"e": {"f": {"token": "***"}}}}}}}, + ), + # Other sensitive key names recognised by should_hide_value_for_key. + ( + {"a": {"b": {"c": {"d": {"e": {"secret": "leaked"}}}}}}, + {"a": {"b": {"c": {"d": {"e": {"secret": "***"}}}}}}, + ), + ( + {"a": {"b": {"c": {"d": {"e": {"api_key": "leaked"}}}}}}, + {"a": {"b": {"c": {"d": {"e": {"api_key": "***"}}}}}}, + ), + ], + ) + def test_redact_sensitive_key_past_max_depth(self, val, expected): + secrets_masker = SecretsMasker() + configure_secrets_masker_for_test(secrets_masker) + with patch( + "airflow_shared.secrets_masker.secrets_masker._secrets_masker", return_value=secrets_masker + ): + assert redact(val) == expected + def test_redact_with_str_type(self, logger, caplog): """ SecretsMasker's re replacer has issues handling a redactable item of type
cde4885818beUpdating release notes for 3.2.2rc3
2 files changed · +5 −4
RELEASE_NOTES.rst+3 −2 modified@@ -24,7 +24,7 @@ .. towncrier release notes start -Airflow 3.2.2 (2026-05-27) +Airflow 3.2.2 (2026-05-29) -------------------------- Significant Changes @@ -81,7 +81,8 @@ Significant Changes Bug Fixes ^^^^^^^^^ - +- Fix ``Callback.handle_event`` triggerer crash when OpenTelemetry metrics receive dict typed tag values (#67527) (#67529) +- UI: Rewrite ``modulepreload hrefs`` to the api-server static path (#67548) (#67556) - Correctly pre-allocate ``external_executor_id`` with multiple executors on PostgreSQL (#67388) (#67458) - Return raw import-error stacktrace when a Dag file has no registered Dag (#67465) (#67478) - UI: Fix Expand/Collapse All on XComs and Audit Log JSON cells (#67316) (#67361)
reproducible_build.yaml+2 −2 modified@@ -1,2 +1,2 @@ -release-notes-hash: 6407b48d1054fe3ce68c09bf4435d91d -source-date-epoch: 1779745327 +release-notes-hash: 504288db9a9dc13a0db859232fab98d0 +source-date-epoch: 1779811737
Vulnerability mechanics
No source-code context for this CVE — mechanics is only generated when we can read the actual fix diff. Without that, the four sections (root cause, attack vector, affected code, fix) would be speculation rather than analysis.
References
2News mentions
0No linked articles in our index yet.