VYPR
High severityNVD Advisory· Published Sep 7, 2024· Updated Sep 9, 2024

Apache Airflow: Authenticated DAG authors could execute code on scheduler nodes

CVE-2024-45034

Description

Apache Airflow versions before 2.10.1 have a vulnerability that allows DAG authors to add local settings to the DAG folder and get it executed by the scheduler, where the scheduler is not supposed to execute code submitted by the DAG author. Users are advised to upgrade to version 2.10.1 or later, which has fixed the vulnerability.

AI Insight

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

Apache Airflow before 2.10.1 allows authenticated DAG authors to execute arbitrary code on scheduler nodes by placing malicious local settings in the DAG folder.

Vulnerability

Details

Apache Airflow versions before 2.10.1 contain a vulnerability that allows DAG authors to execute arbitrary code on the scheduler node [2]. The root cause is that the scheduler's initialization process added the DAGs folder to the system path (sys.path) before importing local settings files (e.g., airflow_local_settings). This allowed any code placed in the DAG folder by a DAG author to be automatically loaded and executed by the scheduler, which is not intended to run user-submitted code [4].

Exploitation

An attacker must be an authenticated DAG author with the ability to write files to the DAG folder. By creating a specially crafted airflow_local_settings.py or similar configuration file containing malicious code, the scheduler would execute it during startup or reload. No additional authentication or network position is required beyond standard DAG author privileges [3].

Impact and

Mitigation

A successful exploit could allow an attacker to execute arbitrary Python code on the scheduler node, potentially leading to full compromise of the Airflow environment, including access to sensitive data and infrastructure. The vulnerability is rated as important severity [2]. The fix in version 2.10.1 separates the syspath preparation into stages, ensuring the DAGs folder is added only after local settings are imported, thus preventing unauthorized code execution [4]. Users are advised to upgrade to Apache Airflow 2.10.1 or later; no workarounds are available [3].

AI Insight generated on May 20, 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
< 2.10.12.10.1

Affected products

9

Patches

1
03e01e76d220

Splitting syspath preparation into stages (#41672) (#41694)

https://github.com/apache/airflowTzu-ping ChungAug 23, 2024via ghsa
2 files changed · +37 13
  • airflow/settings.py+10 7 modified
    @@ -675,11 +675,8 @@ def configure_action_logging() -> None:
         """Any additional configuration (register callback) for airflow.utils.action_loggers module."""
     
     
    -def prepare_syspath():
    -    """Ensure certain subfolders of AIRFLOW_HOME are on the classpath."""
    -    if DAGS_FOLDER not in sys.path:
    -        sys.path.append(DAGS_FOLDER)
    -
    +def prepare_syspath_for_config_and_plugins():
    +    """Update sys.path for the config and plugins directories."""
         # Add ./config/ for loading custom log parsers etc, or
         # airflow_local_settings etc.
         config_path = os.path.join(AIRFLOW_HOME, "config")
    @@ -690,6 +687,12 @@ def prepare_syspath():
             sys.path.append(PLUGINS_FOLDER)
     
     
    +def prepare_syspath_for_dags_folder():
    +    """Update sys.path to include the DAGs folder."""
    +    if DAGS_FOLDER not in sys.path:
    +        sys.path.append(DAGS_FOLDER)
    +
    +
     def get_session_lifetime_config():
         """Get session timeout configs and handle outdated configs gracefully."""
         session_lifetime_minutes = conf.get("webserver", "session_lifetime_minutes", fallback=None)
    @@ -771,12 +774,13 @@ def import_local_settings():
     def initialize():
         """Initialize Airflow with all the settings from this file."""
         configure_vars()
    -    prepare_syspath()
    +    prepare_syspath_for_config_and_plugins()
         configure_policy_plugin_manager()
         # Load policy plugins _before_ importing airflow_local_settings, as Pluggy uses LIFO and we want anything
         # in airflow_local_settings to take precendec
         load_policy_plugins(POLICY_PLUGIN_MANAGER)
         import_local_settings()
    +    prepare_syspath_for_dags_folder()
         global LOGGING_CLASS_PATH
         LOGGING_CLASS_PATH = configure_logging()
         State.state_color.update(STATE_COLORS)
    @@ -806,7 +810,6 @@ def is_usage_data_collection_enabled() -> bool:
     MEGABYTE = KILOBYTE * KILOBYTE
     WEB_COLORS = {"LIGHTBLUE": "#4d9de0", "LIGHTORANGE": "#FF9933"}
     
    -
     # Updating serialized DAG can not be faster than a minimum interval to reduce database
     # write rate.
     MIN_SERIALIZED_DAG_UPDATE_INTERVAL = conf.getint("core", "min_serialized_dag_update_interval", fallback=30)
    
  • tests/core/test_settings.py+27 6 modified
    @@ -115,21 +115,42 @@ def teardown_method(self):
             for mod in [m for m in sys.modules if m not in self.old_modules]:
                 del sys.modules[mod]
     
    +    @mock.patch("airflow.settings.prepare_syspath_for_config_and_plugins")
         @mock.patch("airflow.settings.import_local_settings")
    -    @mock.patch("airflow.settings.prepare_syspath")
    -    def test_initialize_order(self, prepare_syspath, import_local_settings):
    +    @mock.patch("airflow.settings.prepare_syspath_for_dags_folder")
    +    def test_initialize_order(
    +        self,
    +        mock_prepare_syspath_for_dags_folder,
    +        mock_import_local_settings,
    +        mock_prepare_syspath_for_config_and_plugins,
    +    ):
             """
    -        Tests that import_local_settings is called after prepare_classpath
    +        Tests that import_local_settings is called between prepare_syspath_for_config_and_plugins
    +        and prepare_syspath_for_dags_folder
             """
             mock_local_settings = mock.Mock()
    -        mock_local_settings.attach_mock(prepare_syspath, "prepare_syspath")
    -        mock_local_settings.attach_mock(import_local_settings, "import_local_settings")
    +
    +        mock_local_settings.attach_mock(
    +            mock_prepare_syspath_for_config_and_plugins, "prepare_syspath_for_config_and_plugins"
    +        )
    +        mock_local_settings.attach_mock(mock_import_local_settings, "import_local_settings")
    +        mock_local_settings.attach_mock(
    +            mock_prepare_syspath_for_dags_folder, "prepare_syspath_for_dags_folder"
    +        )
     
             import airflow.settings
     
             airflow.settings.initialize()
     
    -        mock_local_settings.assert_has_calls([call.prepare_syspath(), call.import_local_settings()])
    +        expected_calls = [
    +            call.prepare_syspath_for_config_and_plugins(),
    +            call.import_local_settings(),
    +            call.prepare_syspath_for_dags_folder(),
    +        ]
    +
    +        mock_local_settings.assert_has_calls(expected_calls)
    +
    +        assert mock_local_settings.mock_calls == expected_calls
     
         def test_import_with_dunder_all_not_specified(self):
             """
    

Vulnerability mechanics

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

References

7

News mentions

0

No linked articles in our index yet.