VYPR
Low severityNVD Advisory· Published Dec 14, 2020· Updated Feb 13, 2025

CVE-2020-17511

CVE-2020-17511

Description

In Airflow versions prior to 1.10.13, when creating a user using airflow CLI, the password gets logged in plain text in the Log table in Airflow Metadatase. Same happened when creating a Connection with a password field.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

In Apache Airflow <1.10.13, passwords passed via CLI during user/connection creation are logged in plain text in the metadata database.

Description

CVE-2020-17511 is a vulnerability in Apache Airflow versions prior to 1.10.13 where passwords provided through the command-line interface (CLI) when creating a user or a connection are stored in plain text in the Log table of the Airflow metadata database [1][2]. This occurs because the CLI does not mask password arguments before logging them, exposing sensitive credentials in the application's logs [3].

Exploitation

Exploitation requires an attacker to have access to the Airflow metadata database logs, typically through an authenticated session with read access to the database or via a compromised database backup. The attacker does not need to run the CLI themselves; they only need to be able to retrieve logs that were generated during legitimate administrator actions [2][3].

Impact

Successful exploitation leads to the exposure of plain-text passwords for Airflow users and external connections managed by the platform. An attacker with these credentials could gain unauthorized access to Airflow accounts or to external systems integrated via connections, potentially compromising sensitive data and workflows [1][2].

Mitigation

The vulnerability is fixed in Airflow version 1.10.13, where the CLI now masks password parameters such as --password and --conn-password with asterisks before logging [4]. Users should upgrade to Airflow 1.10.13 or later. There are no known workarounds for versions below 1.10.13; immediate upgrading is recommended [3].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
apache-airflowPyPI
< 1.10.131.10.13

Affected products

3

Patches

1
4e32546faf22

Mask Password in Log table when using the CLI (#11468)

https://github.com/apache/airflowKaxil NaikOct 12, 2020via ghsa
2 files changed · +60 2
  • airflow/utils/cli.py+13 1 modified
    @@ -105,8 +105,20 @@ def _build_metrics(func_name, namespace):
         :param namespace: Namespace instance from argparse
         :return: dict with metrics
         """
    +    sensitive_fields = {'-p', '--password', '--conn-password'}
    +    full_command = list(sys.argv)
    +    for idx, command in enumerate(full_command):  # pylint: disable=too-many-nested-blocks
    +        if command in sensitive_fields:
    +            # For cases when password is passed as "--password xyz" (with space between key and value)
    +            full_command[idx + 1] = "*" * 8
    +        else:
    +            # For cases when password is passed as "--password=xyz" (with '=' between key and value)
    +            for sensitive_field in sensitive_fields:
    +                if command.startswith(f'{sensitive_field}='):
    +                    full_command[idx] = f'{sensitive_field}={"*" * 8}'
    +
         metrics = {'sub_command': func_name, 'start_datetime': datetime.utcnow(),
    -               'full_command': '{}'.format(list(sys.argv)), 'user': getpass.getuser()}
    +               'full_command': '{}'.format(full_command), 'user': getpass.getuser()}
     
         if not isinstance(namespace, Namespace):
             raise ValueError("namespace argument should be argparse.Namespace instance,"
    
  • tests/utils/test_cli_util.py+47 1 modified
    @@ -16,12 +16,16 @@
     # specific language governing permissions and limitations
     # under the License.
     #
    -
    +import json
     import os
    +import sys
     import unittest
     from argparse import Namespace
     from contextlib import contextmanager
     from datetime import datetime
    +from unittest import mock
    +
    +from parameterized import parameterized
     
     from airflow import settings
     from airflow.exceptions import AirflowException
    @@ -85,6 +89,48 @@ def test_get_dags(self):
             with self.assertRaises(AirflowException):
                 cli.get_dags(None, "foobar", True)
     
    +    @parameterized.expand(
    +        [
    +            (
    +                "airflow users create -u test2 -l doe -f jon -e jdoe@apache.org -r admin --password test",
    +                "airflow users create -u test2 -l doe -f jon -e jdoe@apache.org -r admin --password ********"
    +            ),
    +            (
    +                "airflow users create -u test2 -l doe -f jon -e jdoe@apache.org -r admin -p test",
    +                "airflow users create -u test2 -l doe -f jon -e jdoe@apache.org -r admin -p ********"
    +            ),
    +            (
    +                "airflow users create -u test2 -l doe -f jon -e jdoe@apache.org -r admin --password=test",
    +                "airflow users create -u test2 -l doe -f jon -e jdoe@apache.org -r admin --password=********"
    +            ),
    +            (
    +                "airflow users create -u test2 -l doe -f jon -e jdoe@apache.org -r admin -p=test",
    +                "airflow users create -u test2 -l doe -f jon -e jdoe@apache.org -r admin -p=********"
    +            ),
    +            (
    +                "airflow connections add dsfs --conn-login asd --conn-password test --conn-type google",
    +                "airflow connections add dsfs --conn-login asd --conn-password ******** --conn-type google",
    +            )
    +        ]
    +    )
    +    def test_cli_create_user_supplied_password_is_masked(self, given_command, expected_masked_command):
    +        args = given_command.split()
    +
    +        expected_command = expected_masked_command.split()
    +
    +        exec_date = datetime.utcnow()
    +        namespace = Namespace(dag_id='foo', task_id='bar', subcommand='test', execution_date=exec_date)
    +        with mock.patch.object(sys, "argv", args):
    +            metrics = cli._build_metrics(args[1], namespace)
    +
    +        self.assertTrue(metrics.get('start_datetime') <= datetime.utcnow())
    +
    +        log = metrics.get('log')
    +        command = json.loads(log.extra).get('full_command')  # type: str
    +        # Replace single quotes to double quotes to avoid json decode error
    +        command = json.loads(command.replace("'", '"'))
    +        self.assertEqual(command, expected_command)
    +
     
     @contextmanager
     def fail_action_logger_callback():
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.