This is less a front-door smash and more a valet handing every guest the same parking ticket
CVE-2025-21618 is an *improper authentication/session isolation* flaw in NiceGUI On Air, the framework's remote-sharing feature. In affected nicegui versions <=2.9.0, once one user authenticates to an app exposed through On Air, other browsers—including incognito sessions—can inherit that authenticated state. The vendor fixed it in 2.9.1, and the patch shows the root cause plainly: shared client cookies were being retained between On Air requests.
The vendor's HIGH 7.5 score is technically understandable if you score this as generic unauthenticated network auth bypass. In the real world, it overstates enterprise urgency because exploitation depends on a narrower setup: the app must be using ui.run(on_air=True) or an On Air token, the app must actually rely on NiceGUI auth, and the exposed population is far smaller than ordinary web apps. This is a real bug, but for most estates it is a *demo/tunnel edge-case*, not a Monday-morning fleet fire.
4 steps from start to impact.
Find a live On Air app
ui.run(on_air=True) or a persistent On Air token, creating a public URL reachable over the internet. Tooling is trivial here: a normal browser or curl is enough; no exploit framework is required. Reference: NiceGUI On Air docs.- Target uses NiceGUI
- Target explicitly enabled NiceGUI On Air
- Attacker can reach the generated public URL
- On Air is optional and described as a *tech preview*, so most enterprise NiceGUI deployments will not use it
- Many internal NiceGUI apps are local-only or reverse-proxied normally, not published via On Air
Wait for a legitimate login
- Application actually implements authentication with NiceGUI
- At least one real user logs in to the same exposed On Air app
- If the app has no auth, there is nothing to bypass
- If no one logs in, the bug never amplifies into unauthorized access
- Single-user demos or low-traffic tools may never present an exploitable moment
Inherit authenticated state
self.client.cookies.clear() after each response, confirming cookie persistence was the core defect. A browser is sufficient to weaponize this; the 'exploit' is simply browsing the app after another user authenticates. Reference: fix commit.- Vulnerable version is installed
- On Air request handling still shares cookie state
- Blast radius is usually limited to that one app/session context, not host compromise
- Impact depends on what the app lets an authenticated user do
Abuse application-level privileges
- Authenticated user role has meaningful permissions in the app
- No evidence this crosses tenant boundaries beyond the exposed app
- No code execution or server takeover is implied by the advisory
on_air where you do not absolutely need it — Remove the vulnerable exposure path entirely by stopping use of ui.run(on_air=True) or persistent On Air tokens on affected apps. For a MEDIUM verdict there is no noisgate mitigation SLA, but this is the fastest risk reduction for any internet-exposed demo or operator console.nicegui <=2.9.0, then manually confirm which instances actually use On Air. For MEDIUM, there is no separate mitigation deadline—fold this straight into the remediation workflow before the 365-day patch window closes.- Generic EDR does not stop this; the abuse happens inside normal web traffic and the browser/app session layer.
- Password complexity or resets do not fix shared-session leakage; the issue is cookie isolation in On Air request handling, not weak credentials.
- A WAF is unlikely to help because there is no malicious payload pattern to block—just ordinary requests benefiting from bad session handling.
The supporting signals.
| In-the-wild status | No authoritative evidence of active exploitation found in CISA KEV or vendor materials as of this assessment. |
|---|---|
| KEV status | Not listed in the CISA Known Exploited Vulnerabilities Catalog. |
| PoC availability | No standalone public exploit repo surfaced in primary-source review. The best public reproduction artifact is the vendor/GitHub advisory description plus the one-line fix in nicegui/air.py. |
| EPSS | User-supplied EPSS is 0.00172; third-party rollups around this CVE show roughly ~33rd percentile, which is *low* relative exploitation likelihood, not breakout behavior. |
| CVSS meaning | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N scores like a broad internet auth bypass, but the vector misses the real deployment friction: On Air must be enabled first. |
| Affected versions | GitHub advisory says <=2.9.0; NVD says 'prior to 2.9.1'. Operationally, treat all nicegui releases through 2.9.0 as affected *when On Air is used*. |
| Fixed version | Upgrade to 2.9.1 or later. No distro backport evidence surfaced in the primary sources reviewed; this looks like a normal PyPI/library upgrade case. |
| Exposure population | Exposure is not 'all NiceGUI apps'; it is the subset explicitly launched with NiceGUI On Air. The vendor docs describe On Air as an optional public-sharing feature and *tech preview*, which sharply narrows reachable population. |
| Root cause | The patch added self.client.cookies.clear() after each response to stop HTTP client cookie persistence leaking from one On Air request into another. |
| Disclosure | Published 2025-01-06 by GitHub Security Advisory GHSA-v6jv-p6r8-j78w; NVD still shows the record as not enriched by NVD itself. |
Sources.
Why this verdict
- Vendor baseline starts high because it is a no-auth network-reachable authentication failure with integrity impact once reachable.
- Major friction: optional feature exposure. The attacker position is only 'unauthenticated remote' *if* the target explicitly enabled NiceGUI On Air; otherwise the bug is unreachable from the internet. That implication alone cuts real-world exposed population hard.
- Second friction: requires a legitimate login event. The attacker typically needs another user to authenticate first, which makes this more like session bleed than a self-contained pre-auth break-in.
- Third friction: application-scoped blast radius. Impact is limited to what that one NiceGUI app exposes; there is no evidence of framework-to-host compromise, RCE, or cross-estate takeover.
- Low threat intel pressure. No KEV listing, no primary-source evidence of campaigns, and a low EPSS signal all argue against keeping the vendor 7.5 intact.
Why not higher?
If this were a default-on web admin surface or a broadly exposed SaaS control plane, the vendor HIGH would stand. But this vulnerability lives behind an optional relay feature and a specific auth usage pattern, which means the exposed population is much smaller than the CVSS vector implies.
Why not lower?
This is still a *real* authentication bypass on publicly reachable apps that use On Air, and exploitation is operationally simple once the conditions are present. For teams that do use On Air for demos, robotics, support, or ad hoc remote access, the impact can be immediate unauthorized access to authenticated functions.
Crowdsourced verification payload.
Run this on the target host, container image, venv, or CI build environment where nicegui is installed. Invoke it with python3 check_nicegui_cve_2025_21618.py; no admin privileges are needed, but it must run in the same Python environment as the app package.
#!/usr/bin/env python3
"""
Check for CVE-2025-21618 in nicegui.
Outputs one of: VULNERABLE / PATCHED / UNKNOWN
Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
"""
import sys
import os
import re
from pathlib import Path
try:
from importlib import metadata as importlib_metadata
except Exception:
import importlib_metadata # type: ignore
def parse_version(v):
nums = []
for part in re.findall(r"\d+", v):
try:
nums.append(int(part))
except ValueError:
nums.append(0)
while len(nums) < 3:
nums.append(0)
return tuple(nums[:3])
def main():
try:
version = importlib_metadata.version('nicegui')
except importlib_metadata.PackageNotFoundError:
print('UNKNOWN: nicegui is not installed in this Python environment')
sys.exit(2)
except Exception as e:
print(f'UNKNOWN: unable to read installed package metadata: {e}')
sys.exit(2)
installed = parse_version(version)
vulnerable_floor = (0, 0, 0)
fixed = (2, 9, 1)
# Try to inspect the installed source to detect source/backport fixes.
source_check = None
try:
import nicegui.air as air # type: ignore
air_file = Path(getattr(air, '__file__', ''))
if air_file and air_file.exists():
text = air_file.read_text(encoding='utf-8', errors='ignore')
if 'self.client.cookies.clear()' in text:
source_check = 'patched'
else:
source_check = 'not_patched'
except Exception:
source_check = None
if source_check == 'patched':
print(f'PATCHED: nicegui {version} has the cookie-clear fix present in air.py')
sys.exit(0)
if vulnerable_floor <= installed < fixed:
print(f'VULNERABLE: nicegui {version} is older than 2.9.1 and no backport fix was detected')
sys.exit(1)
if installed >= fixed:
print(f'PATCHED: nicegui {version} is 2.9.1 or newer')
sys.exit(0)
print(f'UNKNOWN: unable to determine CVE-2025-21618 status for nicegui {version}')
sys.exit(2)
if __name__ == '__main__':
main()