CVE-2026-45307
Description
Speakr is a personal, self-hosted web application designed for transcribing audio recordings. Prior to 0.8.20-alpha, the is_safe_url() helper used to validate post-login redirect targets applied urljoin(request.host_url, target) before parsing, while the controller passed the raw target to redirect(). A scheme-relative input such as ////evil.com resolved to a same-host URL during validation but was emitted verbatim in the Location header, where the browser interpreted it as a network-path-relative redirect to an attacker-controlled host. This vulnerability is fixed in 0.8.20-alpha.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Speakr before 0.8.20-alpha has an open redirect via a parser mismatch in the `is_safe_url()` helper.
Vulnerability
Speakr, a personal self-hosted web application for transcribing audio, contained an open redirect vulnerability in versions prior to 0.8.20-alpha. The is_safe_url() helper used to validate post-login redirect targets applied urljoin(request.host_url, target) before parsing, while the controller passed the raw target to redirect(). A scheme-relative input such as ////evil.com resolved to a same-host URL during validation but was emitted verbatim in the Location header, where the browser interpreted it as a network-path-relative redirect to an attacker-controlled host [1].
Exploitation
An attacker can craft a Speakr-hosted login URL with a malicious next parameter. For example, requesting GET /login?next=%2F%2F%2F%2Fevil.com causes is_safe_url('////evil.com') to return True, and the response sets Location: ////evil.com, which browsers follow to https://evil.com/. No authentication or special privileges are required; the attacker only needs to lure an authenticated user to the crafted link [1].
Impact
The attacker gains the ability to redirect authenticated users to an arbitrary external site after sign-in. This enables phishing chains that begin from a trusted Speakr deployment, potentially leading to credential theft or other malicious outcomes [1].
Mitigation
The vulnerability is fixed in version 0.8.20-alpha. The fix rewrites is_safe_url() to validate the raw target against a local-path allowlist: leading / is required; scheme-relative URLs (//, \), backslashes, control characters, and any value with a scheme or netloc are rejected. The duplicate copy in src/api/auth.py was removed, and regression tests were added. Users on 0.8.19-alpha or earlier who cannot upgrade immediately can front Speakr with a reverse proxy that strips next query parameters on the /login route, or block requests where next starts with //, \, or contains a scheme [1].
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2<0.8.20-alpha+ 1 more
- (no CPE)range: <0.8.20-alpha
- (no CPE)range: <0.8.20-alpha
Patches
0No patches discovered yet.
Vulnerability mechanics
Root cause
"Parser mismatch: `urljoin()` normalizes scheme-relative inputs to same-host URLs during validation, but the raw input is passed verbatim to the `Location` header, where the browser interprets it as a network-path-relative redirect."
Attack vector
An attacker crafts a Speakr-hosted login URL with a `next` parameter containing a scheme-relative input such as `////evil.com`. The `is_safe_url()` helper resolves this to a same-host URL during validation (returning `True`), but the controller passes the raw `////evil.com` verbatim to the `Location` header. The browser interprets the four leading slashes as a network-path-relative redirect, following it to `https://evil.com/` [ref_id=1]. The attack requires no authentication and only that the victim clicks the crafted link and logs in.
Affected code
The vulnerability resides in the `is_safe_url()` helper used to validate post-login redirect targets. The function applied `urljoin(request.host_url, target)` before parsing, while the controller passed the raw target to `redirect()`. A duplicate copy of the flawed logic existed in `src/api/auth.py` [ref_id=1].
What the fix does
The fix rewrites `is_safe_url()` to validate the raw target against a local-path allowlist: a leading `/` is required, and scheme-relative URLs (`//`, `/\`), backslashes, control characters, and any value with a scheme or netloc are rejected. The duplicate copy in `src/api/auth.py` was removed so that password login and the SSO next/callback flow share a single utility. Regression tests were added in `tests/test_open_redirect.py` [ref_id=1].
Preconditions
- inputVictim must click a crafted Speakr-hosted login URL and complete authentication
- networkAttacker-controlled external site must be reachable by the victim's browser
Reproduction
Send a GET request to `/login?next=%2F%2F%2F%2Fevil.com`. The `is_safe_url('////evil.com')` call returns `True`, and the response sets `Location: ////evil.com`, which browsers follow to `https://evil.com/` [ref_id=1].
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
1News mentions
0No linked articles in our index yet.