Any Key Will Do: SSH Host Key Checks Off by Default in Apache Airflow
Apache Airflow's SSH and SFTP connections skipped host key verification by default, so a worker would accept any server that answered. That hands a machine-in-the-middle the credentials and data of every task. This is CVE-2026-94134 and CVE-2026-94135, and the only warning was a line in a log.
CVE-2026-94134high 8.1
Here is the short version. Apache Airflow runs data pipelines, and those pipelines constantly open SSH and SFTP connections to other servers to run commands and move files. The first thing SSH is supposed to do on every connection is check that the server on the other end is the server you meant to reach, by matching its host key. Airflow’s SSH and SFTP providers turned that check off by default. Unless you had gone out of your way to configure a host key, Airflow accepted whatever key the server offered, silently, every time.
That means a worker will happily complete a handshake with an impostor. Anyone sitting on the network path between Airflow and the real server can stand in the middle, present their own key, be accepted, and read the credentials and data the task sends. The bug is not loud. There is no error, no failed connection, no prompt. The only trace is a warning buried in the task log, written after the insecure connection is already open, which nobody reads. These are CVE-2026-94134 and CVE-2026-94135.
Let me build it up from the basics.
What Airflow is and why it opens so many SSH connections
Apache Airflow is a workflow orchestrator. You describe a pipeline as a set of tasks with an order, and Airflow schedules and runs them. It is the backbone of a huge amount of data engineering: extract data from here, transform it there, load it somewhere, on a schedule, with retries and monitoring.
A lot of that work happens on other machines. So Airflow has the idea of a connection, a stored set of credentials and settings for talking to an external system, and a matching hook, the code that uses a connection. The SSH provider and the SFTP provider give you an SSHHook to run commands on a remote host over SSH, and an SFTPHook to move files over SFTP. A busy Airflow deployment opens these connections constantly, often carrying service-account credentials with real access to production servers.
Which means the question “are we sure we are talking to the right server?” is not academic here. It runs thousands of times a day.
Host key verification, in plain terms
When you connect to a server over SSH, the server proves its identity with a host key, a cryptographic key pair unique to that machine. Your client remembers the key the first time (this is the “The authenticity of host … can’t be established” prompt you have seen), stores it in a known_hosts file, and on every later connection checks that the server still presents the same key. If the key suddenly changes, the connection is refused, because a changed key is what an impostor looks like. This is how SSH resists a machine-in-the-middle: someone who can intercept your traffic cannot impersonate the server without its private key.
Airflow’s providers are built on Paramiko, a Python SSH library, and Paramiko makes you choose a policy for what to do when a server’s key is not already known:
- RejectPolicy refuses the unknown key. This matches what OpenSSH does, and it is the safe choice.
- WarningPolicy logs a warning but connects anyway.
- AutoAddPolicy silently accepts the key and connects. Convenient, and completely without protection, because it accepts any key, including an attacker’s.
The async SFTP path uses a different library, asyncssh, where the equivalent of “verify nothing” is passing known_hosts=None.
The bug is which default Airflow picked.
The default that turns the check off
The SSH hook hardcoded verification off before it even looked at your connection settings. In the constructor:
1
2
# providers/ssh/.../hooks/ssh.py (before the fix)
self.no_host_key_check = True
Then, when the connection is actually made, that flag selects the unsafe policy and writes the warning that arrives too late to matter:
1
2
3
if self.no_host_key_check:
self.log.warning("No Host Key Verification. This won't protect against Man-In-The-Middle attacks")
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # nosec B507
That # nosec B507 comment is its own small tell. It tells the Bandit security linter to stop flagging this exact line, the one Bandit flags precisely because AutoAddPolicy enables machine-in-the-middle attacks. The warning was silenced instead of the behavior being fixed.
The SFTP hook had the same default, expressed as a logic quirk. It read no_host_key_check from the connection’s extra field, and when the field was absent, which is the normal state for a freshly created SFTP connection, it defaulted to on:
1
2
nhkc_raw = extra_options.get("no_host_key_check")
no_host_key_check = str(nhkc_raw).lower() == "true" if nhkc_raw is not None else True
When that evaluates to True, the hook sets known_hosts = "none", which becomes known_hosts=None for the client, and verification is disabled.
CVE-2026-94135 is the same defect on Airflow’s asynchronous, deferrable SFTP path: SFTPHookAsync, SFTPTrigger, and SFTPClientPool, built on asyncssh. They carried the identical default, so when no_host_key_check was not explicitly set, they passed known_hosts=None to asyncssh and verified nothing. It is tracked separately because it affects deferrable sensors and operators independently of the synchronous hook, but it is the same root cause, and the same release fixes both.
The one and only signal that any of this happened was that log.warning line, written after the insecure connection was already established. In practice it is invisible.
The attack
There is no exploit chain to speak of, which is part of why it matters. The precondition is a position on the network path between the Airflow worker and the server it connects to. That position is realistic in plenty of environments: another host on a shared subnet using ARP spoofing, a compromised switch or router, manipulated routing inside a cloud VPC, or a BGP hijack for the ambitious.
From there:
- An Airflow task opens an SSH or SFTP connection using the default configuration.
- The attacker intercepts the connection and presents their own key pair.
- Airflow’s policy,
AutoAddPolicyfor SSH orknown_hosts=Nonefor SFTP, accepts the key without complaint. - The handshake completes against the attacker’s server. No error, no UI alert, nothing but a log line nobody is watching.
- The attacker now reads whatever the task sends: the credentials used to authenticate, the commands, the files. They can tamper with command output, alter files in transit, or quietly harvest the credentials and reuse them against the real server later.
Proof of concept
The demonstration is deliberately dull, because the point is that nothing goes wrong from Airflow’s side. A small script stands up a throwaway SSH server with a freshly generated key that belongs to no legitimate host, then connects to it with the same AutoAddPolicy Airflow used. The connection succeeds. The fake key is accepted with no error and no warning to the operator. That is the entire vulnerability: a client that treats a brand-new, unknown key as trustworthy is a client with no defense against impersonation. A public proof of concept by OX Security is linked in the references.
What is at risk
The connections this affects are not low-value. SSH and SFTP connections in Airflow routinely carry service-account credentials with shell access to production servers, and the data moving across them feeds downstream databases and warehouses. A successful interception means stolen credentials that work directly against the target, tampered command output that the pipeline trusts, and modified files that can propagate into stored data before anyone notices. And because the weakness is a silent default rather than a misconfiguration anyone had to make, an organization running many SSH and SFTP connections is almost certainly running them unverified without knowing it.
The fix
The fix landed in version 7.0.0 of both providers, apache-airflow-providers-ssh and apache-airflow-providers-sftp (PR #73419, merged October 7, 2026). It is a breaking change, on purpose:
- The default flips. Host key verification is now on. A connection to a host with no known key is refused, exactly like OpenSSH, unless you opt out deliberately.
- A real opt-out exists. A
no_host_key_checkconstructor argument was added toSSHHook, so code that builds a hook directly, not from a stored connection, can also choose its behavior. The constructor argument takes precedence over the connection extra. - A dead setting was revived. An extra called
ignore_hostkey_verificationexisted but did nothing. It is now honored as a deprecated alias forno_host_key_checkand emits a deprecation warning, so connections that set it expecting it to work finally behave as named. - Worker hooks inherit configuration. The concurrent directory transfer helpers used to build per-worker hooks that dropped the parent’s settings; they now inherit the host key configuration, so the secure default is not quietly lost on those paths.
To keep working after upgrading, each existing SSH and SFTP connection needs one of: the server’s key added to known_hosts, a host_key supplied on the connection, or no_host_key_check set explicitly to true if you accept the risk. Setting verification on without providing a key will make the connection fail, which is the secure behavior doing its job, not a bug. Rotate any credentials that may have traveled over unverified connections, and review target-server access logs for sessions you cannot account for.
A note on the score
Reasonable people score this one differently, so here are the numbers side by side. The research rates CVE-2026-94134 at 8.1 High (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N), with the attack complexity marked high because the attacker needs that man-in-the-middle position. The CISA contributor record on NVD rates it 9.1, treating the complexity as low, and scores the async-only CVE-2026-94135 at 4.3. Apache’s own advisory labels both “moderate.” The disagreement is entirely about how hard it is to get into the network path and how much that should weigh. The defect, and the fix, are the same regardless.
Timeline
- Sep 20, 2026: Fix PR #73419 opened against Apache Airflow.
- Oct 7, 2026: Fix merged.
- Oct 13, 2026: Providers 7.0.0 released to PyPI with the fix.
- Oct 15, 2026: Apache publishes the advisories for CVE-2026-94134 and CVE-2026-94135.
- Oct 16, 2026: Full write-up published.
Details and credits
- Component: Apache Airflow SSH provider and SFTP provider
- Class: Missing host key verification, key exchange without entity authentication (CWE-322)
- CVSS: 8.1 High,
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N(NVD’s CISA record rates CVE-2026-94134 at 9.1 withAC:L, and CVE-2026-94135 at 4.3; Apache rates both moderate) - Affected: apache-airflow-providers-ssh and apache-airflow-providers-sftp, all releases before 7.0.0
- Fixed: apache-airflow-providers-ssh 7.0.0 and apache-airflow-providers-sftp 7.0.0 (PR #73419)
- Reported by: Igor Stepansky, OX Security
- Fix by: Jarek Potiuk (Apache Airflow)
References
- OX Security write-up: https://www.ox.security/blog/by-default-insecure-apache-airflow-disables-ssh-host-key-verification/
- Apache advisory, CVE-2026-94134: https://lists.apache.org/thread/ksr1lztvd3zr74f8xd1hzch1ssl0l3lj
- Apache advisory, CVE-2026-94135: https://lists.apache.org/thread/jbzs97t9y1zd1yp5rvxzjbx90mqz82kf
- Fix PR: https://github.com/apache/airflow/pull/73419
- Proof of concept (OX Security): https://github.com/MosesOX/POC-CVE-2026-94134
- NVD entry: https://nvd.nist.gov/vuln/detail/CVE-2026-94134
- CWE-322 (Key Exchange without Entity Authentication): https://cwe.mitre.org/data/definitions/322.html