This is a side door that only exists if you built a hallway to it
CVE-2026-49448 is an authentication-flow bypass in authentik's Source stage: when the current stage is SourceStage, an attacker can send an empty POST and incorrectly advance the flow. The vendor advisory marks the affected supported lines as <=2025.12.5, <=2026.2.3, and <=2026.5.0, with fixes called out as 2025.12.6, 2026.2.4, and 2026.5.1; the release notes show the GHSA landing in 2025.12.6, 2026.2.4, and on the 2026.5 line under 2026.5.2 because 2026.5.1 was skipped.
The vendor's 9.8/CRITICAL score is technically understandable but operationally too hot. This is not a universal unauthenticated login bypass across every authentik deployment; it only matters where a reachable authentication flow actually includes a Source stage, the source exposes a login button/browser flow, and the next stages let the attacker convert that stage skip into a session. That's still bad for exposed IdPs, but it's configuration-gated enough to downgrade from CRITICAL to HIGH.
4 steps from start to impact.
Reach a flow that embeds a SourceStage
Source stage. In practice that means the tenant is using an external OAuth/SAML source inside the login flow rather than only offering local password/WebAuthn or direct source login.- Internet or internal network reachability to the authentik login flow
- A flow bound to a public-facing application or default login path
- That flow contains a
Source stage
- Many authentik deployments do not use
Source stagein their main login flow - Some orgs use sources directly rather than embedding them in another flow
Advance to the source step
SourceStage is current. The vendor PoC shows a flow where username-only identification gets the user to the source step before any real credential check occurs.- The flow exposes an identification or routing stage before the source
- The attacker can supply a target identifier or otherwise reach the source transition
- If password, WebAuthn, or CAPTCHA gates exist before the source, the chain gets harder or breaks
- Flows that do not reveal or route to a source for attacker-controlled input reduce reachability
Send an empty POST instead of returning from the IdP
SourceStage, the attacker sends an empty POST to the flow executor. Per the advisory, SourceStageView.dispatch() falls through, ChallengeResponse(data={}).is_valid() succeeds, and challenge_valid() calls stage_ok(), incorrectly advancing the plan.- Current stage is
SourceStage - Vulnerable authentik build
- The source exposes a browser/UI login path
- This is brittle outside the exact vulnerable flow path
- WAFs rarely understand authentik stage state, but custom request validation could still interfere
SourceStage completion without a matching source callback or restore token.Convert the skipped stage into a real session
UserLoginStage and authenticates the session without the user ever touching the upstream IdP.- A login or authorization-granting stage follows the source
- No later stage re-verifies with password, MFA, or another independent factor
- Any downstream MFA/password stage collapses the exploit into a partial bypass instead of account takeover
- Blast radius is limited to flows built this way, not the whole product by default
The supporting signals.
| In-the-wild status | No reviewed evidence of active exploitation, and not present in CISA KEV in the reviewed catalog source. |
|---|---|
| Proof-of-concept availability | A working vendor PoC is embedded in the GitHub advisory as a regression test showing login without touching the IdP; that is enough for reliable reproduction by defenders and attackers alike. |
| EPSS | No authoritative EPSS value was surfaced in the reviewed sources for this CVE; treat EPSS as not yet useful here rather than as a negative signal. |
| KEV status | Not KEV-listed in the reviewed CISA catalog source. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H maps to a pure network, unauthenticated bypass on paper, but that score ignores the very real deployment prerequisite that a vulnerable Source stage flow must exist and be reachable. |
| Affected versions | Vendor advisory lists supported affected lines as <=2025.12.5, <=2026.2.3, and <=2026.5.0. |
| Fixed versions | Authoritative fix points are 2025.12.6 and 2026.2.4. The advisory says 2026.5.1, but the release notes show 2026.5.1 was skipped and the GHSA appears under 2026.5.2; in practice, use 2026.5.2+ on that train. |
| Exploit prerequisites | Exploitability depends on a flow using Source stage, a source with a browser login path / ui_login_button, attacker reachability to that stage, and no later independent auth stage that re-checks identity. |
| Scanning / exposure reality | I did not find a trustworthy public Shodan/Censys/GreyNoise count specific to this CVE in reviewed primary sources. What we can say from vendor docs is that authentik is commonly placed on the login edge and sources can be shown on the default login page or embedded into flows, so affected instances are often internet-reachable if this feature is used. |
| Disclosure / reporter | GitHub advisory GHSA-xp7f-xjjx-gwm8 was published by rissson on 2026-05-28; your intel block lists CVE disclosure as 2026-06-02, which is likely the downstream CVE publication date. |
noisgate verdict.
The single biggest downward pressure is that exploitation requires a specific vulnerable flow design, not merely a vulnerable package version. I kept this in HIGH because if that flow exists on an internet-facing IdP, the result can still be direct account takeover into downstream SSO-protected apps.
Why this verdict
- Downgrade for deployment friction: vendor CVSS assumes any network-adjacent authentik is instantly exploitable, but the flaw only bites where a public flow actually contains a reachable
Source stage. - Compounding prerequisites matter: the attacker often needs a specific login path, a source-enabled identification flow, and a post-source stage that does not re-check credentials. Each prerequisite narrows the exposed population.
- Still HIGH because the blast radius is identity-tier: when the vulnerable flow exists on an internet-facing IdP, the attacker can turn one stage skip into real SSO access, which can fan out into many downstream apps.
Why not higher?
This is not the kind of bug that automatically compromises every authentik instance at version X. The exploit chain collapses if the deployment does not use Source stage, if the attacker cannot reach that stage, or if a later password/MFA stage independently validates the user.
Why not lower?
The absence of KEV does not make this harmless. A published unauthenticated auth bypass in an edge identity product, with vendor PoC and straightforward reproduction, is still dangerous enough that exposed deployments using the feature should not treat it as routine backlog.
What to do — in priority order.
- Audit every public auth flow for
Source stage— Identify whereSource stageis actually bound into internet-facing authentication or authorization flows, and scope your exposure to those instances first. For a HIGH verdict, complete this inventory and any immediate access restrictions within 30 days. - Remove or disable
Source stageon exposed flows where it is not essential — If the source hop is only for convenience or migration cleanup, take it out of the public path until patched. This directly breaks the exploit chain and should be deployed within 30 days. - Require an independent auth factor after the source hop — Where business requirements force
Source stageto stay, add or enforce a downstream password, WebAuthn, or MFA stage that does not rely on the source transition alone. That turns a stage skip into a failed login path and should be enforced within 30 days. - Restrict reachability to vulnerable login surfaces — Put exposed authentik entry points behind IP allowlists, VPN, private access proxies, or equivalent edge controls where feasible, especially for admin, migration, and partner-specific flows. Use this as an interim mitigation within 30 days while patching proceeds.
- Hunt for source-stage completions without source callbacks — Build detections around flow progression anomalies:
SourceStagecompletion or session creation without corresponding upstream source/IdP callback events. Start this hunt immediately and operationalize alerts within 30 days.
- Upstream MFA at the external IdP does not save you if the attacker skips the source interaction entirely.
- Patching only outposts or reverse proxies is not sufficient; the vulnerable logic is in the authentik flow executor path.
- Generic WAF signatures are weak here because the exploit is an application-state bug, not a loud payload exploit.
Crowdsourced verification payload.
Run this from an auditor workstation or any host that can reach the authentik web UI. Invoke it as python3 check_authentik_cve_2026_49448.py https://auth.example.com or python3 check_authentik_cve_2026_49448.py --version 2026.2.3; no admin privileges are required, and no authentication is needed if the instance exposes a normal login page.
#!/usr/bin/env python3
# check_authentik_cve_2026_49448.py
# Detect likely exposure to CVE-2026-49448 by version.
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN / could not determine
import re
import sys
import ssl
import urllib.request
from urllib.parse import urlparse
TIMEOUT = 10
UA = "noisgate-authentik-check/1.0"
def parse_version(v):
m = re.fullmatch(r"(\d{4})\.(\d+)\.(\d+)", v.strip())
if not m:
return None
return tuple(int(x) for x in m.groups())
def cmp_ver(a, b):
return (a > b) - (a < b)
def classify(version_str):
v = parse_version(version_str)
if not v:
return ("UNKNOWN", f"Unrecognized version format: {version_str}")
# Vendor advisory: affected <=2025.12.5, <=2026.2.3, <=2026.5.0
# Patched: 2025.12.6, 2026.2.4, 2026.5.1 (release skipped; release notes show GHSA in 2026.5.2)
# Operationally, treat 2026.5.2+ as patched on the 2026.5 line.
if v[0] == 2025 and v[1] == 12:
if cmp_ver(v, (2025, 12, 5)) <= 0:
return ("VULNERABLE", f"Version {version_str} is in affected range <=2025.12.5")
return ("PATCHED", f"Version {version_str} is newer than 2025.12.5")
if v[0] == 2026 and v[1] == 2:
if cmp_ver(v, (2026, 2, 3)) <= 0:
return ("VULNERABLE", f"Version {version_str} is in affected range <=2026.2.3")
return ("PATCHED", f"Version {version_str} is newer than 2026.2.3")
if v[0] == 2026 and v[1] == 5:
if cmp_ver(v, (2026, 5, 0)) <= 0:
return ("VULNERABLE", f"Version {version_str} is in affected range <=2026.5.0")
if cmp_ver(v, (2026, 5, 2)) >= 0:
return ("PATCHED", f"Version {version_str} is at or beyond the release-notes fix point 2026.5.2")
if cmp_ver(v, (2026, 5, 1)) == 0:
return ("PATCHED", f"Version {version_str} matches advisory patched version; note 2026.5.1 was skipped in release notes")
return ("UNKNOWN", f"Version {version_str} is outside the explicitly documented affected trains")
def fetch(url):
ctx = ssl.create_default_context()
req = urllib.request.Request(url, headers={"User-Agent": UA})
with urllib.request.urlopen(req, timeout=TIMEOUT, context=ctx) as resp:
return resp.read().decode("utf-8", errors="ignore")
def extract_version_from_html(html):
patterns = [
r"version=(\d{4}\.\d+\.\d+)",
r"authentik[:\s]+(\d{4}\.\d+\.\d+)",
r"content=\"(\d{4}\.\d+\.\d+)\"[^>]*name=\"version\"",
r"name=\"version\"[^>]*content=\"(\d{4}\.\d+\.\d+)\"",
]
for pat in patterns:
m = re.search(pat, html, re.IGNORECASE)
if m:
return m.group(1)
return None
def normalize_url(target):
if not re.match(r"^https?://", target):
target = "https://" + target
p = urlparse(target)
base = f"{p.scheme}://{p.netloc}"
return [base + "/if/flow/default-authentication-flow/", base + "/", target]
def main():
args = sys.argv[1:]
if not args or args[0] in ("-h", "--help"):
print("Usage: python3 check_authentik_cve_2026_49448.py <url> | --version <YYYY.M.P>")
sys.exit(2)
if args[0] == "--version":
if len(args) != 2:
print("UNKNOWN - missing version string")
sys.exit(2)
status, reason = classify(args[1])
print(f"{status} - {reason}")
sys.exit(0 if status == "PATCHED" else 1 if status == "VULNERABLE" else 2)
target = args[0]
errors = []
for url in normalize_url(target):
try:
html = fetch(url)
version = extract_version_from_html(html)
if version:
status, reason = classify(version)
print(f"{status} - detected authentik version {version} from {url}; {reason}")
sys.exit(0 if status == "PATCHED" else 1 if status == "VULNERABLE" else 2)
errors.append(f"No version string found at {url}")
except Exception as e:
errors.append(f"{url}: {e}")
print("UNKNOWN - could not determine authentik version. " + " | ".join(errors))
sys.exit(2)
if __name__ == "__main__":
main()
If you remember one thing.
Source stage in reachable auth flows, because those are the ones that matter. For this HIGH verdict, the noisgate mitigation SLA is within 30 days: disable or isolate vulnerable source-backed flows, or add an independent downstream auth check where removal is not possible. Then complete the actual server upgrade to fixed builds (2025.12.6+, 2026.2.4+, and on the 2026.5 line use 2026.5.2+) within the noisgate remediation SLA of 180 days, with exposed IdPs and migration/partner login flows at the front of the queue this week.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.