This is leaving your master badge accepted at every office door in the building instead of just your own suite
The bug is not in authentication itself; it is in *where the browser sends the Airflow session cookie*. For affected Apache Airflow releases, the _token cookie is scoped to / instead of the configured base_url, so any sibling application under the same parent domain can receive it in request headers. The public records are slightly inconsistent on version bounds: the narrative description says 3.1.0 through 3.1.7, while the NVD/GitHub advisory affected-range metadata says >=3.0.0, <3.1.8; either way, 3.1.8 is the fixed release.
The vendor's *HIGH 7.5* score overrates real enterprise risk. Raw CVSS treats this like a clean unauthenticated network confidentiality issue, but the actual attack path needs a very specific topology: Airflow must share a parent domain with another app the attacker controls or has already compromised, and users must browse both. That is real and worth fixing, but it is not the same thing as an internet-drive-by Airflow takeover.
4 steps from start to impact.
Find a shared-domain Airflow deployment
curl, or reverse-proxy configs, the attacker identifies an Airflow instance mounted on a shared hostname, often under a subpath such as /team-a. The flaw only matters when the deployment model gives the browser a reason to send Airflow cookies to sibling apps on that same domain.- Airflow is on an affected release before
3.1.8 - Airflow is reachable on a hostname that also serves other applications
- The organization uses path-based routing or same-domain multi-app hosting
- Dedicated Airflow hostnames kill most of the risk immediately
- Many enterprises isolate admin tooling on separate subdomains or VPN-only ingress
- Version scanners can find Airflow, but they cannot prove the vulnerable topology
Gain control of a sibling app on the same domain
_token cookie.- Attacker controls or compromises another app under the same parent domain
- Victims can browse to that sibling app
- The sibling app can log request headers or otherwise read inbound cookies
- This is the decisive brake on severity: it usually implies prior compromise, risky multi-tenant hosting, or a self-inflicted shared-ingress design
- NGFW/WAF do not stop a legitimate browser from sending cookies to a same-domain sibling path
- If no untrusted sibling app exists, there is no theft path
Cookie: headers containing Airflow _token values in non-Airflow application logs. Most vuln scanners miss this completely.Harvest the session token from normal victim traffic
_token with path /. When that same browser visits the sibling app, it includes the Airflow cookie in the request automatically, and the sibling app can capture it from headers without touching Airflow directly.- A valid Airflow user session exists
- Victim browses to the sibling app after login
- Cookie scope remains
/instead of the intended Airflow base path
- Short session lifetimes reduce the value of stolen cookies
- User behavior matters; if users never hit the sibling app, no token is exposed
- Some deployments may already run at
/, where the intended path narrowing benefit is absent
Replay the token for Airflow session takeover
_token with a browser extension, Burp Suite, or curl against the Airflow UI or API and inherits the victim's session. Impact can be full session takeover, including admin actions, but only within the victim's Airflow privileges.- Captured token is still valid
- Airflow accepts the replayed session token
- Victim account has meaningful Airflow permissions
- MFA helps at login, but not after a bearer-style session cookie is stolen
- Blast radius is limited to the stolen user's Airflow role, not blanket infrastructure compromise
- Session invalidation or restart after upgrade can burn captured tokens
The supporting signals.
| In-the-wild status | No public exploitation evidence in the cited sources. This CVE is *not* presented as exploited by CISA KEV, and the reviewed advisories do not mention active campaigns. |
|---|---|
| Proof-of-concept availability | No stand-alone exploit repo surfaced in the reviewed sources, but the bug is trivial to reproduce from the vendor fix discussion in PR #62771. This is a *low-complexity abuse pattern*, not a specialized exploit chain. |
| EPSS | 0.031% and 9th percentile per the GitHub advisory, which is consistent with a niche, topology-dependent issue rather than a broad internet exploitation wave. |
| KEV status | Not listed in the CISA Known Exploited Vulnerabilities catalog. That removes the strongest urgency signal. |
| CVSS vector reality check | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N scores it like a clean remote confidentiality loss. In practice, the *real* prerequisite is same-domain sibling-app control, which CVSS does not model well. |
| Affected versions | There is an advisory mismatch: the description text in NVD and GHSA says 3.1.0 through 3.1.7, while their affected-version metadata says >=3.0.0, <3.1.8. Treat anything before 3.1.8 as suspect until your package inventory proves otherwise. |
| Fixed versions | Vendor fix shipped in 3.1.8, documented in the Airflow 3.1.8 release notes and the GitHub advisory. I did not find distro backport guidance in the reviewed sources. |
| Topology prerequisite | The vendor patch text is explicit: the bug matters because the cookie is sent to *other applications that run under the same domain*; see PR #62771 and the Openwall disclosure. That is the main downward pressure on severity. |
| Scanning / exposure data | No reliable public GreyNoise/Censys/Shodan count in the reviewed sources isolates *this* issue. *Inference:* internet exposure alone is a poor proxy because the decisive condition is shared-domain co-hosting, which external scanners usually cannot fingerprint. |
| Disclosure and credit | Disclosed on 2026-03-17. Openwall credits Daniel Wolf as finder and remediation developer. |
noisgate verdict.
The decisive factor is that exploitation normally requires control of a *different* application on the same parent domain, which is a major real-world narrowing of the reachable population. Impact after theft is real session takeover, but this is mostly a shared-hosting and post-compromise amplifier, not an internet-scale initial access bug.
Why this verdict
- Downgraded for attacker position: the attacker usually needs control of a sibling app on the same domain, which implies prior compromise, risky multi-tenant hosting, or an internal/shared platform design.
- Downgraded for exposed population: only Airflow deployments sharing a parent domain with other reachable apps are meaningfully exposed; dedicated hostnames make the bug mostly irrelevant.
- Kept at MEDIUM for impact: if the prerequisite is met, the attacker can steal a live Airflow session and inherit the victim's privileges with no need to attack Airflow directly.
- Downgraded for threat intel: EPSS is extremely low, there is no KEV entry, and the reviewed sources do not describe active campaigns.
- Notably, even the disclosure mail says
Severity: Medium: that lines up better with real operator risk than the 7.5 CVSS label.
Why not higher?
This is not a straightforward unauthenticated remote compromise of Airflow. The exploit chain depends on same-domain co-hosting and a second app the attacker can run or compromise, which dramatically cuts both the exposed population and the likelihood of internet-scale abuse. There is also no public exploitation evidence pushing this into emergency territory.
Why not lower?
Once the prerequisite exists, the impact is not cosmetic; it is full Airflow session hijack. In organizations where platform teams multiplex admin apps behind one shared ingress, this can become a meaningful lateral-movement shortcut, especially against privileged Airflow users.
What to do — in priority order.
- Move Airflow to a dedicated hostname — Stop sharing the parent domain with unrelated applications so the browser never has a sibling app to send the Airflow cookie to. For a
MEDIUMverdict there is no mitigation SLA, but this is the cleanest architectural fix to schedule before or alongside the upgrade and complete within the 365-day remediation window. - Eliminate path-based multi-app co-hosting for admin tools — If Airflow is mounted at
/team-a,/airflow, or similar behind a shared reverse proxy, split it onto its own DNS name or trust boundary. There is no mitigation SLA forMEDIUM, so do this in the next normal change cycle rather than as an emergency block. - Reduce session lifetime and invalidate old sessions after upgrade — Short-lived sessions reduce the usable window for stolen
_tokenvalues, and forced re-authentication burns any previously harvested cookies after you patch. ForMEDIUM, handle this during standard ops change management and finish actual remediation within 365 days. - Review sibling-app logging for inbound cookies — Check same-domain app and reverse-proxy logs for unexpected Airflow
_tokencookies landing outside Airflow paths. This is detection-heavy rather than preventive, and forMEDIUMit belongs in the next routine detection engineering sprint.
- A WAF does not fix this; the browser is legitimately sending the cookie to a same-domain sibling app, so there is no obvious attack payload to block.
- MFA does not save you after the token is stolen; session replay happens *after* authentication.
- TLS everywhere is still necessary, but it does not address the core flaw because the leak occurs in normal HTTPS requests to another app under the same domain.
Crowdsourced verification payload.
Run this on the Airflow host, container, or image build pipeline where apache-airflow is installed. Invoke it as python3 check_cve_2026_28779.py /etc/airflow/airflow.cfg; no admin rights are required unless the config file is root-readable only. The script checks the installed Airflow version and the configured AIRFLOW__WEBSERVER__BASE_URL / AIRFLOW__API__BASE_URL or values from airflow.cfg, then prints VULNERABLE, PATCHED, or UNKNOWN.
#!/usr/bin/env python3
# check_cve_2026_28779.py
# Detect likely exposure to CVE-2026-28779 on an Airflow node/container.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
import configparser
import os
import sys
from urllib.parse import urlparse
try:
from importlib import metadata as importlib_metadata
except Exception:
import importlib_metadata # type: ignore
def parse_version(v):
parts = []
for token in str(v).split('.'):
num = ''
for ch in token:
if ch.isdigit():
num += ch
else:
break
parts.append(int(num) if num else 0)
while len(parts) < 3:
parts.append(0)
return tuple(parts[:3])
def version_in_range(v, low_incl, high_excl):
return parse_version(low_incl) <= parse_version(v) < parse_version(high_excl)
def get_installed_version():
candidates = ['apache-airflow', 'apache_airflow']
for name in candidates:
try:
return importlib_metadata.version(name)
except Exception:
continue
return None
def read_cfg(path):
data = {}
if not path or not os.path.isfile(path):
return data
cp = configparser.ConfigParser()
try:
cp.read(path)
except Exception:
return data
if cp.has_section('webserver') and cp.has_option('webserver', 'base_url'):
data['webserver_base_url'] = cp.get('webserver', 'base_url').strip()
if cp.has_section('api') and cp.has_option('api', 'base_url'):
data['api_base_url'] = cp.get('api', 'base_url').strip()
return data
def path_from_url(value):
if not value:
return None
try:
parsed = urlparse(value)
path = parsed.path or '/'
return path
except Exception:
return None
def non_root_path(value):
p = path_from_url(value)
if p is None:
return False
return p not in ('', '/')
def main():
cfg_path = sys.argv[1] if len(sys.argv) > 1 else os.environ.get('AIRFLOW_CONFIG') or '/etc/airflow/airflow.cfg'
version = get_installed_version()
if not version:
print('UNKNOWN - apache-airflow package version not found')
sys.exit(2)
cfg = read_cfg(cfg_path)
web_base = os.environ.get('AIRFLOW__WEBSERVER__BASE_URL') or cfg.get('webserver_base_url')
api_base = os.environ.get('AIRFLOW__API__BASE_URL') or cfg.get('api_base_url')
affected = version_in_range(version, '3.0.0', '3.1.8')
fixed = parse_version(version) >= parse_version('3.1.8')
if fixed:
print(f'PATCHED - apache-airflow {version} >= 3.1.8')
sys.exit(0)
if not affected:
print(f'UNKNOWN - apache-airflow {version} is outside the published affected range used by this check')
sys.exit(2)
# Practical exposure check: bug matters when Airflow is intended to live under a subpath.
web_non_root = non_root_path(web_base)
api_non_root = non_root_path(api_base)
if web_non_root or api_non_root:
details = []
if web_non_root:
details.append(f'webserver.base_url={web_base}')
if api_non_root:
details.append(f'api.base_url={api_base}')
print('VULNERABLE - apache-airflow {} with non-root base URL(s): {}'.format(version, ', '.join(details)))
sys.exit(1)
if web_base or api_base:
print('UNKNOWN - apache-airflow {} is in affected versions, but configured base URLs appear root-scoped; validate shared-domain co-hosting manually'.format(version))
sys.exit(2)
print('UNKNOWN - apache-airflow {} is in affected versions, but base_url settings were not found; inspect AIRFLOW__WEBSERVER__BASE_URL, AIRFLOW__API__BASE_URL, and reverse-proxy topology manually'.format(version))
sys.exit(2)
if __name__ == '__main__':
main()
If you remember one thing.
3.1.8 and separate the ones on *dedicated hostnames* from the ones sharing a parent domain with other apps or mounted on subpaths; the latter are your real risk set. Because this lands at MEDIUM, there is no noisgate mitigation SLA — go straight to the 365-day remediation window for the actual upgrade, but if you knowingly share a domain across admin tools, fix that design in the next normal change cycle and complete the vendor patch to 3.1.8+ within the noisgate remediation SLA of 365 days.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.