CVE-2026-50552
Koel: Server-Side Request Forgery (SSRF) in radio station creation due to missing validation bail
Koel is a free, open-source music streaming solution. Prior to version 9.7.1, Koel contains a Server-Side Request Forgery (SSRF) vulnerability in the radio station creation endpoint (POST /api/radio/stations). The url field validation rules are declared without the bail keyword, so the HasAudioContentType rule — which issues HTTP requests to the supplied URL — still executes even after the SafeUrl rule has rejected the URL as pointing to a private/reserved address. Any authenticated, non-admin user can therefore coerce the server into making HEAD/GET requests to arbitrary internal hosts. This issue has been patched in version 9.7.1.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- Low
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
6.3
Koel: Server-Side Request Forgery (SSRF) in radio station creation due to missing validation bail
Koel is a free, open-source music streaming solution. Prior to version 9.7.1, Koel contains a Server-Side Request Forgery (SSRF) vulnerability in the radio station creation endpoint (POST /api/radio/stations). The url field validation rules are declared without the bail keyword, so the HasAudioContentType rule — which issues HTTP requests to the supplied URL — still executes even after the SafeUrl rule has rejected the URL as pointing to a private/reserved address. Any authenticated, non-admin user can therefore coerce the server into making HEAD/GET requests to arbitrary internal hosts. This issue has been patched in version 9.7.1.
- Record
CVE-2026-50552- CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
EUVDEUVD-2026-36546
6.3
Koel is a free, open-source music streaming solution. Prior to version 9.7.1, Koel contains a Server-Side Request Forgery (SSRF) vulnerability in the radio station creation endpoint (POST /api/radio/stations). The url field validation rules are declared without the bail keyword, so the HasAudioContentType rule — which issues HTTP requests to the supplied URL — still executes even after the SafeUrl rule has rejected the URL as pointing to a private/reserved address. Any authenticated, non-admin user can therefore coerce the server into making HEAD/GET requests to arbitrary internal hosts. This issue has been patched in version 9.7.1.
- Record
EUVD-2026-36546- CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-jr4p-4xjh-fwvw
6.3
Koel: Server-Side Request Forgery (SSRF) in radio station creation due to missing validation bail
Summary
Koel v9.5.0 contains a Server-Side Request Forgery (SSRF) vulnerability in the radio station creation endpoint (POST /api/radio/stations). The url field validation rules are declared without the bail keyword, so the HasAudioContentType rule — which issues HTTP requests to the supplied URL — still executes even after the SafeUrl rule has rejected the URL as pointing to a private/reserved address. Any authenticated, non-admin user can therefore coerce the server into making HEAD/GET requests to arbitrary internal hosts.
This is a blind SSRF: the response body is never returned to the client, but the two distinct validation error messages form a reliable internal-network reachability oracle.
Severity
Medium — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N (5.4)
Authenticated (low-privilege) actor; blind SSRF (no response-body exfiltration). Confidentiality (L) reflects the internal reachability oracle; Integrity (L) reflects state-changing internal endpoints that act on GET/HEAD.
Affected Component
- Version: Koel v9.5.0 (latest release, HEAD b2e9a34, verified 2026-05-30)
- Endpoint: POST /api/radio/stations
- Files:
- app/Http/Requests/API/Radio/RadioStationStoreRequest.php (missing bail)
- app/Rules/HasAudioContentType.php (unguarded HTTP fetch)
Root Cause
app/Http/Requests/API/Radio/RadioStationStoreRequest.php (lines 25-34):
'url' => [
'required',
'url',
Rule::unique('radio_stations')->where(function ($query) {
return $query->where('user_id', $this->user()->id);
}),
new SafeUrl(),
new HasAudioContentType(),
],
In Laravel, validation rules within a single attribute run in sequence and do not stop on the first failure unless bail is present or FormRequest::$stopOnFirstFailure is set. Neither is the case here (App\Http\Requests\Request does not override stopOnFirstFailure, and authorize() simply returns true).
For a directly-supplied private/reserved address (e.g. http://169.254.169.254, http://127.0.0.1:6379, http://192.168.0.1):
-
SafeUrl (app/Rules/SafeUrl.php lines 45-49) calls isPublicHost($uri->host()), fails it, calls $fail(...) and returns. SafeUrl itself does not make a request in this path — good.
-
Because there is no bail, validation continues to HasAudioContentType.
-
HasAudioContentType::resolveContentType() (app/Rules/HasAudioContentType.php lines 41-57) issues the request with no IP/host validation of its own:
private function resolveContentType(string $url): string { try { $response = Http::head($url); // <-- SSRF if ($response->successful()) { return $response->header('Content-Type'); } } catch (Throwable) { }
// Falls back to a streaming GET $response = Http::withHeaders(['Icy-MetaData' => '1']) ->withOptions(['stream' => true]) ->get($url); // <-- SSRF return $response->header('Content-Type');}
The rule's own docblock (line 13-14) states "Should be used after SafeUrl to ensure the URL is safe to reach." — that assumption is silently violated by the missing bail.
Authorization Context
The endpoint is reachable by any authenticated user, not just admins:
- routes/api.base.php line 108 wraps the route group in Route::middleware('auth') only.
- routes/api.base.php line 268: Route::apiResource('stations', RadioStationController::class).
- RadioStationController::store() (lines 31-37) carries only #[DisabledInDemo] — no $this->authorize(...), no #[RequiresPlus], no permission check.
Reachability Oracle (Information Disclosure)
HasAudioContentType::validate() returns two distinct messages:
- Host unreachable / request throws → "The url couldn't be reached." (line 26)
- Host reachable but Content-Type is not audio/* → "The url doesn't look like a valid radio station URL." (line 32)
By diffing these responses an attacker can…
- Record
GHSA-jr4p-4xjh-fwvw- CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
NVD
6.3
Koel is a free, open-source music streaming solution. Prior to version 9.7.1, Koel contains a Server-Side Request Forgery (SSRF) vulnerability in the radio station creation endpoint (POST /api/radio/stations). The url field validation rules are declared without the bail keyword, so the HasAudioContentType rule — which issues HTTP requests to the supplied URL — still executes even after the SafeUrl rule has rejected the URL as pointing to a private/reserved address. Any authenticated, non-admin user can therefore coerce the server into making HEAD/GET requests to arbitrary internal hosts. This issue has been patched in version 9.7.1.
- Record
CVE-2026-50552- CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
OSV
6.3
Koel: Server-Side Request Forgery (SSRF) in radio station creation due to missing validation bail
Koel is a free, open-source music streaming solution. Prior to version 9.7.1, Koel contains a Server-Side Request Forgery (SSRF) vulnerability in the radio station creation endpoint (POST /api/radio/stations). The url field validation rules are declared without the bail keyword, so the HasAudioContentType rule — which issues HTTP requests to the supplied URL — still executes even after the SafeUrl rule has rejected the URL as pointing to a private/reserved address. Any authenticated, non-admin user can therefore coerce the server into making HEAD/GET requests to arbitrary internal hosts. This issue has been patched in version 9.7.1.
- Record
CVE-2026-50552- CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVGHSA-jr4p-4xjh-fwvw
6.3
Koel: Server-Side Request Forgery (SSRF) in radio station creation due to missing validation bail
Summary
Koel v9.5.0 contains a Server-Side Request Forgery (SSRF) vulnerability in the radio station creation endpoint (POST /api/radio/stations). The url field validation rules are declared without the bail keyword, so the HasAudioContentType rule — which issues HTTP requests to the supplied URL — still executes even after the SafeUrl rule has rejected the URL as pointing to a private/reserved address. Any authenticated, non-admin user can therefore coerce the server into making HEAD/GET requests to arbitrary internal hosts.
This is a blind SSRF: the response body is never returned to the client, but the two distinct validation error messages form a reliable internal-network reachability oracle.
Severity
Medium — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N (5.4)
Authenticated (low-privilege) actor; blind SSRF (no response-body exfiltration). Confidentiality (L) reflects the internal reachability oracle; Integrity (L) reflects state-changing internal endpoints that act on GET/HEAD.
Affected Component
- Version: Koel v9.5.0 (latest release, HEAD b2e9a34, verified 2026-05-30)
- Endpoint: POST /api/radio/stations
- Files:
- app/Http/Requests/API/Radio/RadioStationStoreRequest.php (missing bail)
- app/Rules/HasAudioContentType.php (unguarded HTTP fetch)
Root Cause
app/Http/Requests/API/Radio/RadioStationStoreRequest.php (lines 25-34):
'url' => [
'required',
'url',
Rule::unique('radio_stations')->where(function ($query) {
return $query->where('user_id', $this->user()->id);
}),
new SafeUrl(),
new HasAudioContentType(),
],
In Laravel, validation rules within a single attribute run in sequence and do not stop on the first failure unless bail is present or FormRequest::$stopOnFirstFailure is set. Neither is the case here (App\Http\Requests\Request does not override stopOnFirstFailure, and authorize() simply returns true).
For a directly-supplied private/reserved address (e.g. http://169.254.169.254, http://127.0.0.1:6379, http://192.168.0.1):
-
SafeUrl (app/Rules/SafeUrl.php lines 45-49) calls isPublicHost($uri->host()), fails it, calls $fail(...) and returns. SafeUrl itself does not make a request in this path — good.
-
Because there is no bail, validation continues to HasAudioContentType.
-
HasAudioContentType::resolveContentType() (app/Rules/HasAudioContentType.php lines 41-57) issues the request with no IP/host validation of its own:
private function resolveContentType(string $url): string { try { $response = Http::head($url); // <-- SSRF if ($response->successful()) { return $response->header('Content-Type'); } } catch (Throwable) { }
// Falls back to a streaming GET $response = Http::withHeaders(['Icy-MetaData' => '1']) ->withOptions(['stream' => true]) ->get($url); // <-- SSRF return $response->header('Content-Type');}
The rule's own docblock (line 13-14) states "Should be used after SafeUrl to ensure the URL is safe to reach." — that assumption is silently violated by the missing bail.
Authorization Context
The endpoint is reachable by any authenticated user, not just admins:
- routes/api.base.php line 108 wraps the route group in Route::middleware('auth') only.
- routes/api.base.php line 268: Route::apiResource('stations', RadioStationController::class).
- RadioStationController::store() (lines 31-37) carries only #[DisabledInDemo] — no $this->authorize(...), no #[RequiresPlus], no permission check.
Reachability Oracle (Information Disclosure)
HasAudioContentType::validate() returns two distinct messages:
- Host unreachable / request throws → "The url couldn't be reached." (line 26)
- Host reachable but Content-Type is not audio/* → "The url doesn't look like a valid radio station URL." (line 32)
By diffing these responses an attacker can…
- Record
GHSA-jr4p-4xjh-fwvw- CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
phanan/koelPackagist |
through 9.7.0 | 9.7.1 |
phanan/koelPackagist |
before 9.7.1, plus listed versions 1.0.0-betav0.0.0-betav1.1.1v1.1.2v2.0.0 Show 45 moreShow lessv2.0.1v2.0.2v2.1.0v2.2.0v2.2.1v3.0.0v3.0.1v3.1.0v3.1.1v3.2.0v3.3.0v3.3.1v3.4.0v3.4.1v3.5.0v3.5.1v3.5.2v3.5.3v3.5.4v3.5.5v3.6.0v3.6.1v3.6.2v3.7.0v3.7.1v3.7.2v4.0.0v4.1.0v4.1.1v4.2.0v4.2.1v4.2.2v4.3.0v4.3.1v4.4.0v5.0.0v5.0.1v5.0.2v5.1.0v5.1.1v5.1.10v5.1.11v5.1.12v5.1.13v5.1.14 |
9.7.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-50552 is a medium vulnerability (CVSS 6.3) in Koel, published June 12, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.17%, the 6th percentile. Fixed in phanan/koel 9.7.1.
References
github.com/koel/koel/security/advisories/GHSA-jr4p-4xjh-fwvw
github.com/koel/koel/commit/5f6ce2cefd08f437a269236b677ad971517ccbb6
nvd.nist.gov/vuln/detail/CVE-2026-50552
github.com/koel/koel/pull/2549
github.com/koel/koel/releases/tag/v9.7.1
github.com/advisories/GHSA-jr4p-4xjh-fwvw
github.com/CVEProject/cvelistV5/tree/main/cves/2026/50xxx/CVE-2026-50552.json
github.com/koel/koel
Left out here: 120 versions and the rest of 2 source descriptions. The JSON record has them.