VYPR
Critical severityNVD Advisory· Published Nov 22, 2022· Updated Apr 29, 2025

Apache Airflow Pinot provider allowed Command Injection

CVE-2022-38649

Description

OS command injection in Apache Airflow Pinot Provider allows attackers to execute arbitrary commands in task execution context without write access to DAG files.

AI Insight

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

OS command injection in Apache Airflow Pinot Provider allows attackers to execute arbitrary commands in task execution context without write access to DAG files.

CVE-2022-38649 is an OS command injection vulnerability in the Apache Airflow Pinot Provider, affecting versions prior to 4.0.0, as well as any Airflow version prior to 2.3.0 if the Pinot Provider is installed [2]. The root cause is improper neutralization of special elements used in an OS command, where the PinotAdminHook allowed the attacker-controlled connection parameter cmd_path to be injected into the command executed by pinot-admin.sh [3]. This allowed a malicious actor to control the command path without needing write access to DAG files [1].

To exploit this, an attacker must have the ability to modify Airflow connection configurations, for example, through the Airflow UI or API, as the cmd_path parameter was read from the connection's extra JSON [3]. The attack can be performed by setting the cmd_path to the attacker's executable or script, which is then executed with the privileges of the Airflow worker [1]. The exploitation does not require the attacker to have write access to DAG files, lowering the barrier for exploitation [2].

Successful exploitation allows an attacker to execute arbitrary OS commands in the task execution context, potentially leading to full compromise of the Airflow worker environment, including access to sensitive data, credentials, and the ability to pivot to other systems [1][2]. The impact is high, as Airflow workers often have access to various databases, cloud services, and other infrastructure [1].

Affected users should upgrade to Apache Airflow Pinot Provider version 4.0.0, which hard-codes the pinot-admin.sh command path and prevents the injection [3][4]. For Airflow versions prior to 2.3.0, upgrading Airflow itself to 2.3.0 or later is required before installing the fixed Pinot Provider [2]. No workarounds are provided by the vendor, and the vulnerability is not listed on CISA's Known Exploited Vulnerabilities catalog at the time of writing.

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
< 2.3.02.3.0

Affected products

4

Patches

1
1d4fd5c6eaca

The pinot-admin.sh command is now hard-coded. (#27641)

https://github.com/apache/airflowJarek PotiukNov 13, 2022via ghsa
4 files changed · +49 7
  • airflow/providers/apache/pinot/CHANGELOG.rst+22 0 modified
    @@ -24,6 +24,28 @@
     Changelog
     ---------
     
    +4.0.0
    +.....
    +
    +This release of provider is only available for Airflow 2.3+ as explained in the Apache Airflow
    +providers support policy https://github.com/apache/airflow/blob/main/README.md#support-for-providers
    +
    +Breaking changes
    +~~~~~~~~~~~~~~~~
    +
    +The admin command is now hard-coded to ``pinot-admin.sh``. The ``pinot-admin.sh`` command must be available
    +on the path in order to use PinotAdminHook.
    +
    +Misc
    +~~~~
    +
    +* ``Move min airflow version to 2.3.0 for all providers (#27196)``
    +* ``Bump pinotdb version (#27201)``
    +
    +.. Below changes are excluded from the changelog. Move them to
    +   appropriate section above if needed. Do not delete the lines(!):
    +   * ``Enable string normalization in python formatting - providers (#27205)``
    +
     3.2.1
     .....
     
    
  • airflow/providers/apache/pinot/hooks/pinot.py+13 3 modified
    @@ -46,7 +46,10 @@ class PinotAdminHook(BaseHook):
         following PR: https://github.com/apache/incubator-pinot/pull/4110
     
         :param conn_id: The name of the connection to use.
    -    :param cmd_path: The filepath to the pinot-admin.sh executable
    +    :param cmd_path: Do not modify the parameter. It used to be the filepath to the pinot-admin.sh
    +           executable but in version 4.0.0 of apache-pinot provider, value of this parameter must
    +           remain the default value: `pinot-admin.sh`. It is left here to not accidentally override
    +           the `pinot_admin_system_exit` in case positional parameters were used to initialize the hook.
         :param pinot_admin_system_exit: If true, the result is evaluated based on the status code.
                                         Otherwise, the result is evaluated as a failure if "Error" or
                                         "Exception" is in the output message.
    @@ -62,7 +65,15 @@ def __init__(
             conn = self.get_connection(conn_id)
             self.host = conn.host
             self.port = str(conn.port)
    -        self.cmd_path = conn.extra_dejson.get("cmd_path", cmd_path)
    +        if cmd_path != "pinot-admin.sh":
    +            raise RuntimeError(
    +                "In version 4.0.0 of the PinotAdminHook the cmd_path has been hard-coded to"
    +                " pinot-admin.sh. In order to avoid accidental using of this parameter as"
    +                " positional `pinot_admin_system_exit` the `cmd_parameter`"
    +                " parameter is left here but you should not modify it. Make sure that "
    +                " `pinot-admin.sh` is on your PATH and do not change cmd_path value."
    +            )
    +        self.cmd_path = "pinot-admin.sh"
             self.pinot_admin_system_exit = conn.extra_dejson.get(
                 "pinot_admin_system_exit", pinot_admin_system_exit
             )
    @@ -213,7 +224,6 @@ def run_cli(self, cmd: list[str], verbose: bool = True) -> str:
     
             if verbose:
                 self.log.info(" ".join(command))
    -
             with subprocess.Popen(
                 command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True, env=env
             ) as sub_process:
    
  • airflow/providers/apache/pinot/provider.yaml+1 0 modified
    @@ -22,6 +22,7 @@ description: |
         `Apache Pinot <https://pinot.apache.org/>`__
     
     versions:
    +  - 4.0.0
       - 3.2.1
       - 3.2.0
       - 3.1.0
    
  • tests/providers/apache/pinot/hooks/test_pinot.py+13 4 modified
    @@ -35,7 +35,7 @@ def setUp(self):
             self.conn = conn = mock.MagicMock()
             self.conn.host = "host"
             self.conn.port = "1000"
    -        self.conn.extra_dejson = {"cmd_path": "./pinot-admin.sh"}
    +        self.conn.extra_dejson = {}
     
             class PinotAdminHookTest(PinotAdminHook):
                 def get_connection(self, conn_id):
    @@ -165,7 +165,7 @@ def test_run_cli_success(self, mock_popen):
     
             params = ["foo", "bar", "baz"]
             self.db_hook.run_cli(params)
    -        params.insert(0, self.conn.extra_dejson.get("cmd_path"))
    +        params.insert(0, "pinot-admin.sh")
             mock_popen.assert_called_once_with(
                 params, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, close_fds=True, env=None
             )
    @@ -180,7 +180,7 @@ def test_run_cli_failure_error_message(self, mock_popen):
             params = ["foo", "bar", "baz"]
             with pytest.raises(AirflowException):
                 self.db_hook.run_cli(params)
    -        params.insert(0, self.conn.extra_dejson.get("cmd_path"))
    +        params.insert(0, "pinot-admin.sh")
             mock_popen.assert_called_once_with(
                 params, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, close_fds=True, env=None
             )
    @@ -196,14 +196,23 @@ def test_run_cli_failure_status_code(self, mock_popen):
             params = ["foo", "bar", "baz"]
             with pytest.raises(AirflowException):
                 self.db_hook.run_cli(params)
    -        params.insert(0, self.conn.extra_dejson.get("cmd_path"))
    +        params.insert(0, "pinot-admin.sh")
             env = os.environ.copy()
             env.update({"JAVA_OPTS": "-Dpinot.admin.system.exit=true "})
             mock_popen.assert_called_once_with(
                 params, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, close_fds=True, env=env
             )
     
     
    +class TestPinotAdminHookCreation:
    +    def test_exception_when_overriding_cmd_path(self):
    +        with pytest.raises(RuntimeError):
    +            PinotAdminHook(cmd_path="some_path.sh")
    +
    +    def test_exception_when_keeping_cmd_path(self):
    +        PinotAdminHook(cmd_path="pinot-admin.sh")
    +
    +
     class TestPinotDbApiHook(unittest.TestCase):
         def setUp(self):
             super().setUp()
    

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.