Who Let the DAGs Out? When Your Orchestrator Plays the Wrong Tune
TL;DR: the 60-second version
- Airflow isn’t “just a scheduler”: it’s a production control plane, and real instances are sitting on the public internet with no authentication.
- We found live ones leaking secrets in plaintext: QuickBooks tokens, AWS/RDS credentials, and a ticket-pricing platform exposing live barcodes for multiple major clients.
- The cause is structural: secrets stashed in Variables and DAG code, templated fields (BashOperator) that execute whatever’s injected, and nothing encrypted by default.
- A new CVE, CVE-2026-45192 on 3.X (the patch has already released): the Connection API redacts secrets by field name, not value, so fields like
webhook_urlcome back in plaintext. - Do this now: find and isolate exposed instances, move secrets into a Secret Manager, rotate anything that ever touched a Variable or DAG, and take the broker and Flower off the public internet.
Everyone runs Apache Airflow. Almost nobody treats it like what it is.
That’s the premise of this piece, and it’s worth sitting with for a second. Apache Airflow is the open-source orchestrator that schedules and runs data pipelines, workflows made of Python tasks. Teams use it to move and transform data (copying last night’s orders into an analytics warehouse), to run machine learning pipelines (retraining a fraud-detection model on yesterday’s data), and to handle recurring ops work: dashboards, backups, log rotation. I used it myself during my thesis, to orchestrate fine-tuning LLMs for secure code generation, which is how I ended up knowing this tool inside out.
This is offensive research: real exposed Apache Airflow instances found in the wild, new findings including one CVE, all verified against Airflow’s current release.
This is the research behind my BSides Las Vegas talk. If you missed it, there’s another chance to catch it live at OWASP Global AppSec on October 6.
It breaks into four parts: what Airflow actually is and how it’s built; what we found exposed in the wild: default creds, leaked secrets, live incidents; the vulnerabilities themselves, with live demos and a CVE I found on the current release; and finally, what to actually do about it.
Credit where it’s due
None of this is a knock on Apache Airflow itself. The Airflow project is a genuinely remarkable piece of engineering, and the maintainers and the broader community deserve real credit. They built an open-source orchestrator that runs a meaningful slice of the world’s data pipelines, kept it evolving through the hard architectural surgery of the 3.x redesign, and did it all in the open, for free, under enormous load. The security model they publish is honest about where its boundaries lie, and the fixes ship: 3.3.0’s TLS work and the fast turnaround on CVE-2026-45192 are proof the team takes this seriously. Every finding in this piece is about how the tool gets deployed, not a failure of the people who build it. Thank you to the Apache Airflow team for the work.
Part 1: Meet Apache Airflow
Architecture: this is not a scheduler, it’s a control plane
A DAG (Directed Acyclic Graph) is Airflow’s workflow map: tasks, order, dependencies, schedule. Around that map sit four components.
Say it plainly: this is not a scheduler. It’s a production control plane. Every box on that diagram is attack surface.

Who’s actually running it
The adoption numbers make the case, but the 17,000+ forks are the real tell: a fork means an organization pulled the codebase in-house for production use, not casual interest.
But the totals are just the setup. The version distribution is the actual story. Real PyPI data, trailing 30 days, roughly 22 million Airflow downloads, as of August 2026: 55% still on 2.x, 44% on 3.x, and 0.4% on 1.x, a branch that’s been end-of-life since 2021. A full year after Airflow 3 shipped, more than half of installs are still on 2.x.

So, why are so many people staying behind?
Airflow 3’s new architecture, and the fault line it created
Airflow 3 was built to answer exactly that question. It replaced direct worker-to-database access with a new Task Execution API, and split the monolithic webserver into a separate API server and DAG processor.
On 2.x, the worker reaches the metadata database directly; it holds the Fernet key and every credential. On 3.x, an API server sits in between; workers talk to it over a short-lived JWT and hold no database credentials at all. That’s a genuine relocation of risk.

But it also created a brand-new boundary: the worker-to-API-server JWT handshake. And it produced a year of 403, 404, and 405 reports as people tried to upgrade. One thing stayed unchanged either way: CeleryExecutor still needs a Redis or RabbitMQ broker sitting in the middle. This split, and the broker that survived it, is the fault line under both of the live exploitation paths covered later in this piece.
The pattern across a year of upgrade reports is consistent, across every Airflow 3 deployment style (Docker Compose, Helm, Kubernetes), same root cause every time: after upgrading, the worker can’t authenticate to the new API server, so its tasks silently stop running. A 405 in May 2025. A 404 on a subpath ingress. 403s turning into 404s. Wrong-audience tokens. Bad-signature tokens, reported all the way through April 2026. Every one of those reports is someone who tried to move to 3.x and hit the same wall. No wonder more than half never left 2.x.
Nothing is encrypted by default
It isn’t just the auth boundary that’s fragile: nothing between these boxes is encrypted by default either. The worker-to-API-server Execution API defaults to plain HTTP on port 8080, and it carries connections, variables, XCom, task state, and the JWT bearer token itself. TLS is opt-in, manual, and historically buggy.
- Execution API: no encryption by default.
- Celery broker: no encryption by default.
- The log server on 8793/8794, bound to
0.0.0.0: no encryption by default. - Metadata DB: no encryption unless you configure it yourself.
There’s no CVE for any of this, because Apache’s own Security Model explicitly states that encryption is the deployment manager’s responsibility, and to be fair, that’s a defensible line to draw. Encrypting every one of these channels in a generic, one-size-fits-all way is genuinely hard: certificates, key rotation, broker choice, and network topology differ wildly between a laptop, a Docker Compose stack, and a locked-down Kubernetes cluster. Airflow can’t safely guess which of those you’re running, so it hands the responsibility to the person who actually installs and deploys it, the one who knows their own environment. That’s a reasonable design decision. It just means the burden is real, and it’s yours. Sniff the wire, and you don’t even need to forge the token; you just read it.
And when people do try to turn TLS on, they hit real bugs. GitHub Discussion #50726: Docker plus a self-signed cert, web UI fine, but worker-to-API runs fail with a certificate verify error. #53493, three months later: the same failure, reported independently, with the reporter asking for a flag to just disable verification outright. Two more discussions show the Task SDK’s httpx client silently ignoring the CA-bundle environment variable the way requests honors it.
On the Celery side: Redis Sentinel SSL is rejected outright; a maintainer has admitted Sentinel was never tested in CI. The rediss:// scheme silently downgrades to redis:// with a warning most people never see. Airflow 3.3.0 did ship real partial fixes, credit where it’s due, but “try to encrypt it, hit a new bug” has repeated for years, across both channels.
Part 2: Wide open, and already exploited
The Shodan reality check
The Shodan query title:"airflow - dags" finds the DAG-list page’s own title with no login wall in front of it, the default on Airflow 1.x, since 2.x forces a “Sign In” page first. The raw hit count looks alarming. Strip the honeypots out, and the real number is small: a handful of genuine, unauthenticated, production Airflow instances. Small doesn’t mean safe. These are real boxes, running real pipelines.

QuickBooks secrets, handed out in plaintext
The first one found: this box’s Variables store hands back a QuickBooks OAuth app’s client secret and refresh token, in plaintext, no authentication required. That pair mints fresh hour-long access tokens for roughly 100 days, full read-write access to a company’s books: invoices, customers, bank transactions. No exploit was needed, no CVE applies. Airflow Variables are not a secrets manager, and this box was using them as one.

A DAG source view, not the Variables page
A different door, the same failure: a construction-management platform. This time the leak wasn’t in Variables; it was the DAG source view, which handed over AWS RDS Postgres credentials, an app login, and a private access key, all hardcoded directly in the DAG code. Someone had already been there: a planted variable curling the cloud metadata endpoint, SSTI probes sitting right beside it. With no vendor relationship available, this one was escalated CERT-to-CERT, across borders.

Airflow 1.10.9: an attacker had already moved in
A third instance, unauthenticated Airflow 1.10.9 (with two known CVEs of its own), contained three attacker-planted variables. Two were Jinja SSTI payloads meant to run OS commands and exfiltrate the output; they failed silently, because Airflow’s macros module doesn’t expose os in that version. Someone had copy-pasted a public payload without checking whether it matched the target version.
But the third payload was real: a full pysftp script, hardcoded credentials, enumerating internal production and UAT SFTP servers, with host-key checking turned off. Read plainly: someone was using an unauthenticated Airflow instance as a trusted execution box inside the perimeter, to run reconnaissance on internal infrastructure that isn’t reachable from the public internet at all.
The primitive underneath all of it
Here’s the shared primitive, and it needs no SSTI gadget whatsoever. bash_command is a templated field: whatever sits in a Variable gets rendered straight into the shell string and executed. The snippet id && whoami && hostname renders verbatim and runs on the worker, no sandbox in the way, on every release. This isn’t an exploit. It’s a feature, used exactly as designed, against you.
The ticket-pricing platform
Some context before the exposure itself: this is a B2B dynamic ticket-pricing platform, the kind of software that sets seat prices in real time from live demand for major events. It’s wired into multiple major clients and live entertainment. Its pipelines push prices into ticketing systems, rebalance inventory, handle returns, and process real ticket barcodes, the sellable asset itself.
Nobody’s heard of this company. But it sits in the middle of the money and the tickets for dozens of major venues.
Found via OSINT in April 2026: Airflow 1.10.15 (end-of-life since 2021), fully unauthenticated, no TLS, admin panel wide open. The DAG list showed a pricing-report and scan-for-barcodes DAG for every client. The same box’s Variables exposed: live third-party API credentials, live ticketing-system credentials for major-league clients, named employee emails (a ready-made spear-phishing list), and the full AWS VPC topology.

The reachable path: not executed. A BashOperator task is arbitrary code execution, and the exposed Variables handed over the full VPC map, confirming this ran on EC2. From a DAG task, the instance metadata service is one HTTP request away: DAG task → IMDS → IAM credentials → account pivot. No screenshot exists for this step because it was not run. This is passive observation: the reachable path, evidenced by the exposed topology, not a performed exploit. Nothing was touched.
Real-world impact. This is where it stops being “an infra bug.” The scan-for-barcodes DAGs run for every one of those 30-plus clients, and the task logs and XCom held real, live ticket barcodes. Pull one from a log, clone it, resell it on a secondary market. Picture it: a fan buys a real seat. Someone clones the barcode straight out of an exposed log. At the gate, the clone scans first. The person who actually paid is the one turned away. That’s not an infrastructure bug. That’s a person, denied entry.
Handled responsibly. Passive observation only: nothing touched, nothing changed, nothing downloaded. With no direct vendor relationship, it was escalated CERT-to-CERT, across borders. The instance has since been confirmed taken offline. This is exactly what CERT-to-CERT coordination exists for.
Default credentials still work
One more, quicker: admin/admin and airflow/airflow ship as Airflow’s quick-start defaults, and neither is forced to rotate. We can’t ethically test how many live instances still accept them; that would mean logging in uninvited, which is exactly the line this research doesn’t cross.
Part 3: The code itself
CVE-2022-40127: Airflow shipped this one themselves
Start with the concrete one, because Airflow shipped it in its own example DAGs. example_bash_operator.py echoed the run_id straight into bash_command. And run_id is literally the “Run id” box on the Trigger-DAG form, so anyone who can trigger a DAG gets OS command injection. That’s CVE-2022-40127, fixed in 2.4.0, and it lived in-tree, enabled by default, until then.
It’s not just an admin typing that input by hand, either: it also arrives through the trigger REST API, through anyone holding Airflow’s User or Op role (the tier normally handed to operators and analysts, not admins), or through an upstream DAG passing along external data.
Broadening the taxonomy: conf injection sits at CVSS 8.8 (CVE-2020-11978); there’s SQL injection through PostgresOperator, remote command execution through SSHOperator, and SSRF through SimpleHttpOperator. But the one worth dwelling on is the bottom row: variable injection through BashOperator. No CVE covers it. No scanner flags it. Full shell on the worker.
Live demo, watched step by step. No terminal needed to prove it. This was demonstrated on Airflow 2.3.4, pre-fix. Current Airflow blocks this exact payload with a run_id format allowlist, but the same unescaped-template primitive still fires through Variables and dag_run.conf params, the exact row highlighted above.



The Redis broker door
The worker-to-API-server boundary isn’t the only surface that only exists once you go distributed. CeleryExecutor inserts a Redis or RabbitMQ broker between the scheduler and the workers, and like that boundary, it simply doesn’t exist on a single node. Redis defaults to no authentication, so a reachable broker is wide open.

Walking it live: redis-cli PING with no auth returns PONG; the broker is open. LRANGE celery 0 -1 dumps the pending task messages, and just reading them leaks dag_id, task_id, and arguments. RPUSH-ing a forged envelope gets a worker to run it; this is the CVE-2020-11981 lineage, and the argv-RCE from that CVE is mitigated on current 2.10.4, but reading the queue, injecting a task, and leaking task context are not.
Shodan data from July 2026: 198 of 211 Redis brokers behind these clusters answered with no authentication. Zero of roughly 2,600 open Flower monitors asked for a login. Read the queue and you read the pipeline. Write to it, and you are the scheduler.
Forging a 3.x JWT
Airflow 3 put workers behind the API server with short-lived JWTs specifically to kill that 2.x blast radius. The token is HS512, signed over a single jwt_secret. If that secret is weak or guessable, it’s possible to forge a token with a completely phantom subject (one that was never a real task) and read decrypted secrets with no running task and no foothold at all, from outside the cluster entirely.
The redesign moved the trust boundary. It didn’t shrink the blast radius if the secret underneath it is weak.
DAG source disclosure: an insecure default, not a CVE
You’ve just seen DAG code be dangerous to run. Here, you don’t even need to run it; you only need to read it. A default Viewer role (explicitly denied the Connections and Variables APIs, 403 on both) can still call GET on dagSources for any DAG ID and get back the full Python source. Hardcoded AWS keys, RDS passwords, all of it, 200 OK. This is not a CVE. It’s an insecure default, working exactly as designed, with no fix planned.
CVE-2026-45192: redaction by name, not by value (Found by Or Sahar)
This is the one, CVE-2026-45192, found on Airflow 3.2.1, fixed in 3.2.2. The Connection API redacts secrets by matching a field’s name against a hardcoded allowlist, not by inspecting what the value actually is. So password and token come back as stars, because those names happen to be on the list. But webhook_url, bearer, or a custom access-key field come back in plaintext, because nobody put those particular names on the list.
A field is only protected if whoever named it happened to pick an allowlisted name. And webhook_url is the exact field name used in Airflow’s own Slack provider documentation. Redaction here isn’t a security boundary; it’s a spell-checker.
Live demo, the payoff: POSTing to that webhook URL posts a message that appears to come from the integration that owns it. No further exploitation needed. Read access to one connection is a working credential.

Part 4: What to actually do about it
This week
Four verbs: find, isolate, fix the defaults, rotate.
- Find and isolate. Inventory your own edges: the Shodan title query, ports 8080, 5555, 6379, 6672. Kill unauthenticated 1.x and 2.x instances on sight, and take the broker and Flower off the public internet.
- Fix the defaults. Secrets belong in a real backend (Vault, SSM, Secrets Manager), never in Variables, never in DAG source. Treat DAGs as a code-execution surface, and restrict who holds the “can read on DAG Code” permission, because source code leaks secrets too.
- Rotate. Anything that ever sat in a Variable, a Connection extra field, or a DAG file should be rotated, full stop.
Not bad luck: a fault line
This is not bad luck. It’s a fault line. It isn’t one bug: it’s exposure, secrets, APIs, and executable code, all converging on the same place. Stop calling it internal tooling. Threat-model it as the production control plane it already is.
Use it well
With great power comes great responsibility. Everything covered here maps to a real, exploitable weakness. Use it to find and fix your own exposed instances, and to disclose responsibly. Never to break into someone else’s.
Or Sahar is a Security Researcher at Reflectiz with two decades of experience breaking, building, and securing software. Connect with her on LinkedIn: securylight.
Frequently Asked Questions
Are Apache Airflow Variables safe for storing secrets?
No. Airflow Variables are not a secrets manager. If an instance is exposed or a user is over-permissioned, the Variables store can hand back API keys, OAuth client secrets, and refresh tokens in plaintext. Secrets should live in a dedicated backend such as HashiCorp Vault, AWS Secrets Manager, or AWS SSM Parameter Store, and should never be hardcoded in Variables or in DAG source code.
Can exposed Apache Airflow instances be found on the internet?
Yes. A Shodan search for the Airflow DAG-list page title surfaces unauthenticated instances, and researchers have found live production deployments leaking real credentials. Shodan data from July 2026 showed that 198 of 211 internet-reachable Redis brokers behind Airflow clusters had no authentication, and none of roughly 2,600 exposed Flower monitoring UIs required a login.
How do you secure an Apache Airflow deployment?
Take Airflow off the public internet and put the web UI, REST API, Celery broker (ports 5555, 6379, and 6672), and Flower behind authentication and network controls. Move every secret into a real secrets backend such as HashiCorp Vault, AWS Secrets Manager, or SSM instead of Variables or DAG code, rotate anything that ever sat in a Variable, a Connection, or a DAG file, restrict who can read DAG source, and retire end-of-life 1.x and 2.x instances.
Is Apache Airflow secure by default?
No. Apache Airflow ships with insecure defaults: the web UI and REST API are internet-facing, the Celery Redis or RabbitMQ broker and the Flower monitoring UI require no authentication, traffic between components is unencrypted over plain HTTP on port 8080, and the quick-start admin/admin and airflow/airflow logins are not forced to rotate. Airflow’s security model treats encryption and network isolation as the deployment operator’s responsibility, so an internet-exposed default install is highly vulnerable.
What is CVE-2022-40127 in Apache Airflow?
CVE-2022-40127 is an OS command-injection vulnerability in Apache Airflow’s bundled example_bash_operator DAG, fixed in Airflow 2.4.0. The DAG rendered the run_id value directly into a bash_command, so anyone who could trigger a DAG through the UI, the REST API, or the User or Op role could run arbitrary commands on the worker. The same unescaped-template pattern still applies to values passed through Variables and dag_run.conf parameters.
What is CVE-2026-45192?
CVE-2026-45192 is an information-disclosure vulnerability in Apache Airflow’s Connection API, affecting all versions before 3.2.2 and fixed in 3.2.2. Airflow redacts secrets by matching a field’s name against a hardcoded allowlist instead of inspecting the value, so fields like webhook_url, bearer, or custom access-key fields are returned in plaintext to any authenticated user with Connection-read permission. The vulnerability was reported by security researcher Or Sahar.
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.