{"id":"CVE-2026-100854","aliases":["EUVD-2026-87834","GHSA-4fm3-ggg2-c6qx","GHSA-m6mg-63cr-44jj"],"status":"published","title":"AzuraCast before 0.23.6 Metadata Injection via Liquidsoap API","description":"AzuraCast before 0.23.6 lacks RequireInternalConnection middleware on the Liquidsoap API endpoint and incorrectly derives the AutoDJ flag from header presence rather than validated value. Users with View station permission can inject arbitrary now-playing metadata, disrupt live broadcasts, and disclose filesystem paths.","severity":{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00195,"percentile":0.09311,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-862"],"published":"2026-05-04T21:18:22Z","modified":"2026-10-28T02:30:49Z","fixes":[{"ecosystem":"Packagist","package":"azuracast/azuracast","fixed_in":["0.23.6"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"azuracast/azuracast","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"0.23.5"},{"fixed":"0.23.6"}]}]},{"source":"osv","ecosystem":"Packagist","package":"azuracast/azuracast","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.23.6"}]}],"versions":["0.10.0","0.10.1","0.10.2","0.10.3","0.10.4","0.11","0.11.1","0.11.2","0.12","0.12.1","0.12.2","0.12.3","0.12.4","0.13.0","0.14.0","0.14.1","0.15.0","0.15.1","0.15.2","0.16.0","0.16.1","0.17.0","0.17.1","0.17.2","0.17.3","0.17.4","0.17.5","0.17.6","0.17.7","0.18.0","0.18.1","0.18.2","0.18.3","0.18.5","0.19.0","0.19.1","0.19.2","0.19.3","0.19.4","0.19.5","0.19.6","0.19.7","0.20.0","0.20.1","0.20.2","0.20.3","0.20.4","0.21.0","0.22.0","0.22.1","0.23.0","0.23.1","0.23.2","0.23.3","0.23.4","0.23.5","0.3.1","0.3.2","0.3.3","0.5.0","0.6.0","0.8.0","0.9.0","0.9.1","0.9.2","0.9.3","0.9.4","0.9.4.1","0.9.4.2","0.9.5","0.9.5.1","0.9.6","0.9.6.1","0.9.6.2","0.9.6.5","0.9.7","0.9.7.1","0.9.8","0.9.8.1","0.9.9"]}],"products":[{"vendor":"azuracast","product":"azuracast"}],"references":[{"url":"https://github.com/AzuraCast/AzuraCast/security/advisories/GHSA-4fm3-ggg2-c6qx","tags":["vendor-advisory","exploit","advisory","web"]},{"url":"https://www.vulncheck.com/advisories/azuracast-before-0.23.6-metadata-injection-via-liquidsoap-api","tags":["third-party-advisory","advisory"]},{"url":"https://github.com/AzuraCast/AzuraCast/commit/13fa7a71435629147b351d3ee151b8de6acd5c8c","tags":["web"]},{"url":"https://github.com/advisories/GHSA-4fm3-ggg2-c6qx"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-100854","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-m6mg-63cr-44jj"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/100xxx/CVE-2026-100854.json","tags":["advisory"]},{"url":"https://github.com/AzuraCast/AzuraCast","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-100854","title":"AzuraCast before 0.23.6 Metadata Injection via Liquidsoap API","description":"AzuraCast before 0.23.6 lacks RequireInternalConnection middleware on the Liquidsoap API endpoint and incorrectly derives the AutoDJ flag from header presence rather than validated value. Users with View station permission can inject arbitrary now-playing metadata, disrupt live broadcasts, and disclose filesystem paths.","status":"published","published":"2026-09-27T01:28:46.412Z","modified":"2026-10-08T15:22:03.45Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N","level":"medium","assessor":"cna"},{"version":"3.1","score":6.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium","assessor":"cna"}],"cwes":["CWE-862"],"updated_at":"2026-11-02T14:20:06.562031Z"}],"euvd":[{"id":"EUVD-2026-87834","description":"AzuraCast before 0.23.6 lacks RequireInternalConnection middleware on the Liquidsoap API endpoint and incorrectly derives the AutoDJ flag from header presence rather than validated value. Users with View station permission can inject arbitrary now-playing metadata, disrupt live broadcasts, and disclose filesystem paths.","status":"published","published":"2026-09-27T01:28:46Z","modified":"2026-10-08T15:22:03Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"updated_at":"2026-11-02T17:27:32.18003Z"}],"ghsa":[{"id":"GHSA-4fm3-ggg2-c6qx","title":"AzuraCast's Missing RequireInternalConnection on Liquidsoap API Allows Low-Privilege Metadata Injection and Broadcast Disruption","description":"## Summary\n\nThe `/api/internal/{station_id}/liquidsoap/{action}` endpoint is accessible from the public web interface because it lacks the `RequireInternalConnection` middleware that protects other internal endpoints (`/sftp-auth`, `/sftp-event`). Combined with a logic flaw where the `$asAutoDj` flag is set based on the *presence* of the `X-Liquidsoap-Api-Key` header rather than its *validated value*, any user with the basic `View` station permission can invoke privileged Liquidsoap commands — injecting arbitrary now-playing metadata visible to all listeners, disrupting live broadcast tracking, and disclosing absolute filesystem paths.\n\n## Details\n\n**Issue 1: Missing RequireInternalConnection middleware**\n\nIn `backend/config/routes/api_internal.php`, the liquidsoap route group (lines 17-21) lacks the `RequireInternalConnection` middleware:\n\n```php\n// Lines 17-21 — NO RequireInternalConnection\n$group-\u003emap(\n    ['GET', 'POST'],\n    '/liquidsoap/{action}',\n    Controller\\Api\\Internal\\LiquidsoapAction::class\n)-\u003esetName('api:internal:liquidsoap');\n```\n\nCompare with sftp endpoints that correctly apply it:\n\n```php\n// Lines 32-34 — HAS RequireInternalConnection\n$group-\u003epost('/sftp-auth', Controller\\Api\\Internal\\SftpAuthAction::class)\n    -\u003esetName('api:internal:sftp-auth')\n    -\u003eadd(Middleware\\RequireInternalConnection::class);\n```\n\nThe nginx config (`util/docker/web/nginx/azuracast.conf.tmpl`) only sets the `IS_INTERNAL` FastCGI parameter on the internal port 6010 listener (line 44), not on the public-facing server block (ports 80/443). Without the middleware, the endpoint is fully accessible from the public internet.\n\n**Issue 2: `$asAutoDj` derived from header presence, not validated value**\n\nIn `backend/src/Controller/Api/Internal/LiquidsoapAction.php`:\n\n```php\n// Line 34 — checks header PRESENCE, not value\n$asAutoDj = $request-\u003ehasHeader('X-Liquidsoap-Api-Key');\n\n// Lines 38-44 — key value only checked when ACL FAILS\n$acl = $request-\u003egetAcl();\nif (!$acl-\u003eisAllowed(StationPermissions::View, $station-\u003eid)) {\n    $authKey = $request-\u003egetHeaderLine('X-Liquidsoap-Api-Key');\n    if (!$station-\u003evalidateAdapterApiKey($authKey)) {\n        throw new RuntimeException('Invalid API key.');\n    }\n}\n```\n\nWhen a user authenticates via session/API key and has `StationPermissions::View`, the ACL check passes and the adapter API key is never validated. But `$asAutoDj` is already `true` from line 34 because the header is present (with any arbitrary value).\n\n**Affected commands:**\n\n- `FeedbackCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/FeedbackCommand.php:36`): Guard `if (!$asAutoDj) return false;` bypassed — creates SongHistory records and forces NowPlaying cache updates\n- `DjOffCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/DjOffCommand.php:24`): Guard bypassed — calls `$this-\u003estreamerRepo-\u003eonDisconnect($station)` which ends all active broadcasts and sets `$station-\u003eis_streamer_live = false`\n- `DjOnCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/DjOnCommand.php:31`): Guard bypassed — calls `$this-\u003estreamerRepo-\u003eonConnect($station, $user)` with attacker-controlled username\n- `CopyCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/CopyCommand.php:18`): No `$asAutoDj` guard at all — returns absolute filesystem paths via `$mediaFs-\u003egetLocalPath($uri)`\n\n## PoC\n\n**Prerequisites:** A user account with `StationPermissions::View` on station ID 1 (the lowest station-level permission). Obtain a session cookie or API key for this user.\n\n**1. Inject arbitrary now-playing metadata (FeedbackCommand):**\n\n```bash\ncurl -X POST 'https://target/api/internal/1/liquidsoap/feedback' \\\n  -H 'X-API-Key: \u003cview-user-api-key\u003e' \\\n  -H 'X-Liquidsoap-Api-Key: anything' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"artist\": \"INJECTED\", \"title\": \"Fake Song Title\"}'\n```\n\nExpected: Should reject — user does not have the adapter API key.\nActual: Returns `true`. The injected artist/title appears in `/api/nowplaying/1` for all listeners.\n\n**2. Disrupt live broadcast (DjOffCommand):**\n\n```bash\ncurl -X POST 'https://target/api/internal/1/liquidsoap/djoff' \\\n  -H 'X-API-Key: \u003cview-user-api-key\u003e' \\\n  -H 'X-Liquidsoap-Api-Key: anything'\n```\n\nExpected: Should reject.\nActual: Returns `true`. All active broadcast records for the station are terminated (`timestampEnd` set), `is_streamer_live` set to `false`, and `current_streamer` cleared.\n\n**3. Disclose filesystem paths (CopyCommand):**\n\n```bash\ncurl -X POST 'https://target/api/internal/1/liquidsoap/cp' \\\n  -H 'X-API-Key: \u003cview-user-api-key\u003e' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"uri\": \"test.mp3\"}'\n```\n\nExpected: Should reject — this is an internal-only endpoint.\nActual: Returns `{\"uri\":\"/var/azuracast/stations/1/media/test.mp3\",\"isTemp\":false}` — disclosing the absolute filesystem path of the station's media storage.\n\n## Impact\n\nAny user with the basic `StationPermissions::View` permission (the lowest station-level role, commonly assigned to DJs and collaborators) can:\n\n1. **Inject arbitrary now-playing metadata** visible to all listeners via the public NowPlaying API and any connected players/widgets. This poisons the song history database and triggers cache updates that propagate the false data to all consumers.\n\n2. **Disrupt live broadcasts** by terminating all active broadcast records and marking the station as having no live streamer, even when a DJ is actively broadcasting. This affects broadcast recording and live-DJ tracking.\n\n3. **Fake DJ connections** with arbitrary usernames via the `djon` command, polluting streamer logs and potentially interfering with DJ scheduling.\n\n4. **Disclose absolute filesystem paths** of the station's media storage directory via the `cp` command (no `$asAutoDj` guard required), which aids further attacks against the server.\n\n## Recommended Fix\n\n**Fix 1: Add `RequireInternalConnection` middleware to the liquidsoap route group.**\n\nIn `backend/config/routes/api_internal.php`, add the middleware to the station group:\n\n```php\n$group-\u003egroup(\n    '/{station_id}',\n    function (RouteCollectorProxy $group) {\n        $group-\u003emap(\n            ['GET', 'POST'],\n            '/liquidsoap/{action}',\n            Controller\\Api\\Internal\\LiquidsoapAction::class\n        )-\u003esetName('api:internal:liquidsoap')\n+           -\u003eadd(Middleware\\RequireInternalConnection::class);\n\n        // Icecast internal auth functions\n        $group-\u003emap(\n            ['GET', 'POST'],\n            '/listener-auth[/{api_auth}]',\n            Controller\\Api\\Internal\\ListenerAuthAction::class\n        )-\u003esetName('api:internal:listener-auth');\n    }\n)-\u003eadd(Middleware\\GetStation::class);\n```\n\n**Fix 2: Validate the API key value before setting `$asAutoDj`.**\n\nIn `backend/src/Controller/Api/Internal/LiquidsoapAction.php`, move `$asAutoDj` assignment after key validation:\n\n```php\n- $asAutoDj = $request-\u003ehasHeader('X-Liquidsoap-Api-Key');\n+ $asAutoDj = false;\n\n  try {\n      $acl = $request-\u003egetAcl();\n      if (!$acl-\u003eisAllowed(StationPermissions::View, $station-\u003eid)) {\n          $authKey = $request-\u003egetHeaderLine('X-Liquidsoap-Api-Key');\n          if (!$station-\u003evalidateAdapterApiKey($authKey)) {\n              throw new RuntimeException('Invalid API key.');\n          }\n+         $asAutoDj = true;\n+     } else {\n+         // Even ACL-authenticated users must provide valid adapter key for AutoDJ operations\n+         $authKey = $request-\u003egetHeaderLine('X-Liquidsoap-Api-Key');\n+         $asAutoDj = !empty($authKey) \u0026\u0026 $station-\u003evalidateAdapterApiKey($authKey);\n      }\n```\n\nBoth fixes should be applied. Fix 1 is the primary defense (defense in depth — this endpoint should never be publicly accessible). Fix 2 corrects the logic flaw so that `$asAutoDj` is only `true` when the adapter API key is actually valid, regardless of how authentication was performed.","status":"published","published":"2026-05-04T21:18:22Z","modified":"2026-05-04T21:18:25Z","cvss":[{"version":"3.1","score":6.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium"}],"cwes":["CWE-862"],"updated_at":"2026-11-02T14:40:00.970271Z"},{"id":"GHSA-m6mg-63cr-44jj","title":"AzuraCast before 0.23.6 lacks RequireInternalConnection middleware on the Liquidsoap API endpoint...","description":"AzuraCast before 0.23.6 lacks RequireInternalConnection middleware on the Liquidsoap API endpoint and incorrectly derives the AutoDJ flag from header presence rather than validated value. Users with View station permission can inject arbitrary now-playing metadata, disrupt live broadcasts, and disclose filesystem paths.","status":"published","published":"2026-09-27T03:31:05Z","modified":"2026-09-27T03:31:12Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium"},{"version":"3.1","score":6.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium"}],"cwes":["CWE-862"],"updated_at":"2026-11-02T15:12:30.689958Z"}],"nvd":[{"id":"CVE-2026-100854","description":"AzuraCast before 0.23.6 lacks RequireInternalConnection middleware on the Liquidsoap API endpoint and incorrectly derives the AutoDJ flag from header presence rather than validated value. Users with View station permission can inject arbitrary now-playing metadata, disrupt live broadcasts, and disclose filesystem paths.","status":"published","published":"2026-09-27T02:17:24.747Z","modified":"2026-10-08T16:17:00.2Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium","assessor":"secondary"},{"version":"3.1","score":6.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium","assessor":"secondary"}],"cwes":["CWE-862"],"updated_at":"2026-11-02T14:12:52.081323Z"}],"osv":[{"id":"CVE-2026-100854","title":"AzuraCast before 0.23.6 Metadata Injection via Liquidsoap API","description":"AzuraCast before 0.23.6 lacks RequireInternalConnection middleware on the Liquidsoap API endpoint and incorrectly derives the AutoDJ flag from header presence rather than validated value. Users with View station permission can inject arbitrary now-playing metadata, disrupt live broadcasts, and disclose filesystem paths.","status":"published","published":"2026-09-27T01:28:46.412Z","modified":"2026-10-28T02:30:49.603099775Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-862"],"updated_at":"2026-11-02T15:44:53.83521Z"},{"id":"GHSA-4fm3-ggg2-c6qx","title":"AzuraCast's Missing RequireInternalConnection on Liquidsoap API Allows Low-Privilege Metadata Injection and Broadcast Disruption","description":"## Summary\n\nThe `/api/internal/{station_id}/liquidsoap/{action}` endpoint is accessible from the public web interface because it lacks the `RequireInternalConnection` middleware that protects other internal endpoints (`/sftp-auth`, `/sftp-event`). Combined with a logic flaw where the `$asAutoDj` flag is set based on the *presence* of the `X-Liquidsoap-Api-Key` header rather than its *validated value*, any user with the basic `View` station permission can invoke privileged Liquidsoap commands — injecting arbitrary now-playing metadata visible to all listeners, disrupting live broadcast tracking, and disclosing absolute filesystem paths.\n\n## Details\n\n**Issue 1: Missing RequireInternalConnection middleware**\n\nIn `backend/config/routes/api_internal.php`, the liquidsoap route group (lines 17-21) lacks the `RequireInternalConnection` middleware:\n\n```php\n// Lines 17-21 — NO RequireInternalConnection\n$group-\u003emap(\n    ['GET', 'POST'],\n    '/liquidsoap/{action}',\n    Controller\\Api\\Internal\\LiquidsoapAction::class\n)-\u003esetName('api:internal:liquidsoap');\n```\n\nCompare with sftp endpoints that correctly apply it:\n\n```php\n// Lines 32-34 — HAS RequireInternalConnection\n$group-\u003epost('/sftp-auth', Controller\\Api\\Internal\\SftpAuthAction::class)\n    -\u003esetName('api:internal:sftp-auth')\n    -\u003eadd(Middleware\\RequireInternalConnection::class);\n```\n\nThe nginx config (`util/docker/web/nginx/azuracast.conf.tmpl`) only sets the `IS_INTERNAL` FastCGI parameter on the internal port 6010 listener (line 44), not on the public-facing server block (ports 80/443). Without the middleware, the endpoint is fully accessible from the public internet.\n\n**Issue 2: `$asAutoDj` derived from header presence, not validated value**\n\nIn `backend/src/Controller/Api/Internal/LiquidsoapAction.php`:\n\n```php\n// Line 34 — checks header PRESENCE, not value\n$asAutoDj = $request-\u003ehasHeader('X-Liquidsoap-Api-Key');\n\n// Lines 38-44 — key value only checked when ACL FAILS\n$acl = $request-\u003egetAcl();\nif (!$acl-\u003eisAllowed(StationPermissions::View, $station-\u003eid)) {\n    $authKey = $request-\u003egetHeaderLine('X-Liquidsoap-Api-Key');\n    if (!$station-\u003evalidateAdapterApiKey($authKey)) {\n        throw new RuntimeException('Invalid API key.');\n    }\n}\n```\n\nWhen a user authenticates via session/API key and has `StationPermissions::View`, the ACL check passes and the adapter API key is never validated. But `$asAutoDj` is already `true` from line 34 because the header is present (with any arbitrary value).\n\n**Affected commands:**\n\n- `FeedbackCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/FeedbackCommand.php:36`): Guard `if (!$asAutoDj) return false;` bypassed — creates SongHistory records and forces NowPlaying cache updates\n- `DjOffCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/DjOffCommand.php:24`): Guard bypassed — calls `$this-\u003estreamerRepo-\u003eonDisconnect($station)` which ends all active broadcasts and sets `$station-\u003eis_streamer_live = false`\n- `DjOnCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/DjOnCommand.php:31`): Guard bypassed — calls `$this-\u003estreamerRepo-\u003eonConnect($station, $user)` with attacker-controlled username\n- `CopyCommand` (`backend/src/Radio/Backend/Liquidsoap/Command/CopyCommand.php:18`): No `$asAutoDj` guard at all — returns absolute filesystem paths via `$mediaFs-\u003egetLocalPath($uri)`\n\n## PoC\n\n**Prerequisites:** A user account with `StationPermissions::View` on station ID 1 (the lowest station-level permission). Obtain a session cookie or API key for this user.\n\n**1. Inject arbitrary now-playing metadata (FeedbackCommand):**\n\n```bash\ncurl -X POST 'https://target/api/internal/1/liquidsoap/feedback' \\\n  -H 'X-API-Key: \u003cview-user-api-key\u003e' \\\n  -H 'X-Liquidsoap-Api-Key: anything' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"artist\": \"INJECTED\", \"title\": \"Fake Song Title\"}'\n```\n\nExpected: Should reject — user does not have the adapter API key.\nActual: Returns `true`. The injected artist/title appears in `/api/nowplaying/1` for all listeners.\n\n**2. Disrupt live broadcast (DjOffCommand):**\n\n```bash\ncurl -X POST 'https://target/api/internal/1/liquidsoap/djoff' \\\n  -H 'X-API-Key: \u003cview-user-api-key\u003e' \\\n  -H 'X-Liquidsoap-Api-Key: anything'\n```\n\nExpected: Should reject.\nActual: Returns `true`. All active broadcast records for the station are terminated (`timestampEnd` set), `is_streamer_live` set to `false`, and `current_streamer` cleared.\n\n**3. Disclose filesystem paths (CopyCommand):**\n\n```bash\ncurl -X POST 'https://target/api/internal/1/liquidsoap/cp' \\\n  -H 'X-API-Key: \u003cview-user-api-key\u003e' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"uri\": \"test.mp3\"}'\n```\n\nExpected: Should reject — this is an internal-only endpoint.\nActual: Returns `{\"uri\":\"/var/azuracast/stations/1/media/test.mp3\",\"isTemp\":false}` — disclosing the absolute filesystem path of the station's media storage.\n\n## Impact\n\nAny user with the basic `StationPermissions::View` permission (the lowest station-level role, commonly assigned to DJs and collaborators) can:\n\n1. **Inject arbitrary now-playing metadata** visible to all listeners via the public NowPlaying API and any connected players/widgets. This poisons the song history database and triggers cache updates that propagate the false data to all consumers.\n\n2. **Disrupt live broadcasts** by terminating all active broadcast records and marking the station as having no live streamer, even when a DJ is actively broadcasting. This affects broadcast recording and live-DJ tracking.\n\n3. **Fake DJ connections** with arbitrary usernames via the `djon` command, polluting streamer logs and potentially interfering with DJ scheduling.\n\n4. **Disclose absolute filesystem paths** of the station's media storage directory via the `cp` command (no `$asAutoDj` guard required), which aids further attacks against the server.\n\n## Recommended Fix\n\n**Fix 1: Add `RequireInternalConnection` middleware to the liquidsoap route group.**\n\nIn `backend/config/routes/api_internal.php`, add the middleware to the station group:\n\n```php\n$group-\u003egroup(\n    '/{station_id}',\n    function (RouteCollectorProxy $group) {\n        $group-\u003emap(\n            ['GET', 'POST'],\n            '/liquidsoap/{action}',\n            Controller\\Api\\Internal\\LiquidsoapAction::class\n        )-\u003esetName('api:internal:liquidsoap')\n+           -\u003eadd(Middleware\\RequireInternalConnection::class);\n\n        // Icecast internal auth functions\n        $group-\u003emap(\n            ['GET', 'POST'],\n            '/listener-auth[/{api_auth}]',\n            Controller\\Api\\Internal\\ListenerAuthAction::class\n        )-\u003esetName('api:internal:listener-auth');\n    }\n)-\u003eadd(Middleware\\GetStation::class);\n```\n\n**Fix 2: Validate the API key value before setting `$asAutoDj`.**\n\nIn `backend/src/Controller/Api/Internal/LiquidsoapAction.php`, move `$asAutoDj` assignment after key validation:\n\n```php\n- $asAutoDj = $request-\u003ehasHeader('X-Liquidsoap-Api-Key');\n+ $asAutoDj = false;\n\n  try {\n      $acl = $request-\u003egetAcl();\n      if (!$acl-\u003eisAllowed(StationPermissions::View, $station-\u003eid)) {\n          $authKey = $request-\u003egetHeaderLine('X-Liquidsoap-Api-Key');\n          if (!$station-\u003evalidateAdapterApiKey($authKey)) {\n              throw new RuntimeException('Invalid API key.');\n          }\n+         $asAutoDj = true;\n+     } else {\n+         // Even ACL-authenticated users must provide valid adapter key for AutoDJ operations\n+         $authKey = $request-\u003egetHeaderLine('X-Liquidsoap-Api-Key');\n+         $asAutoDj = !empty($authKey) \u0026\u0026 $station-\u003evalidateAdapterApiKey($authKey);\n      }\n```\n\nBoth fixes should be applied. Fix 1 is the primary defense (defense in depth — this endpoint should never be publicly accessible). Fix 2 corrects the logic flaw so that `$asAutoDj` is only `true` when the adapter API key is actually valid, regardless of how authentication was performed.","status":"published","published":"2026-05-04T21:18:22Z","modified":"2026-09-27T11:56:07.354866429Z","cvss":[{"version":"3.1","score":6.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","level":"medium"}],"cwes":["CWE-862"],"updated_at":"2026-11-02T15:46:36.782754Z"}]}}
