# CVE-2026-77615: Opencast: Stored XSS in Paella player via WebVTT/DFXP caption cue text

> CVE-2026-77615 (GitHub advisory GHSA-m6c8-jcw2-5r25) is a high-severity vulnerability with a CVSS 3.1 base score of 8.7, classed as Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79), in org.opencastproject:opencast-engage-paella-player-7 (Maven) and paella-core (npm). Fixed versions: org.opencastproject:opencast-engage-paella-player-7 19.7 or 20.2; paella-core 1.50.6.

Canonical page: https://cveminer.com/vulnerabilities/CVE-2026-77615

| Field | Value |
| --- | --- |
| CVE | CVE-2026-77615 |
| GHSA | GHSA-m6c8-jcw2-5r25 |
| Severity | High |
| CVSS 3.1 | 8.7 `CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N` (GitHub Advisory Database) |
| CWE | CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') |
| EPSS | 0.39% (higher than 33% of CVEs) |
| CISA KEV | Not listed |
| Published | 17 Sept 2026 |
| Modified | 18 Sept 2026 |

## Description

The Opencast Paella player renders caption cue text into `innerHTML` without escaping. The captions canvas clears `_captionsContainer.innerHTML` and then appends each active cue with `_captionsContainer.innerHTML += cue`, so HTML inside a WebVTT or DFXP cue becomes live DOM and executes in the Opencast origin.

The caption track is read from any media package element with a `captions/*` flavor and is served, with the player manifest, to anonymous viewers through `/search/episode.json`. The caption plugins that consume it are enabled in the default player configuration, the "Subtitles" upload that produces a `captions/source` track is active by default, and no caption processing step escapes the cue text.

A user who can upload a subtitle to an event and publish it stores the payload in the published caption file. Any viewer who opens the event and turns captions on runs the script.

Result: a non-admin content author stores JavaScript in a subtitle cue that executes in the browser session of every viewer who enables captions on that event, including anonymous viewers and authenticated staff.

#### Affected

opencast/opencast, `engage-paella-player` module. Supported release lines 19.x and 20.x are affected (and 18.x). Live-confirmed on 18.8 (Paella 7, paella-core 1.50.2) and 20.0 (Paella 8, paella-core 1.50.4); 19.5 ships the code-identical caption path (paella-core 1.50.4, same `EpisodeConversor` and default plugin config as 20.0). The captions canvas uses the same `innerHTML += cue` sink across these versions.
Default configuration: the WebVTT and DFXP caption plugins are `enabled: true` in `etc/ui-config/mh_default_org/paella7/config.json`, the "Subtitles" upload option (`captions/source`, `.vtt`) is active in `etc/listproviders/event.upload.asset.options.properties`, and the `fast` workflow publishes `captions/*` to the engage player.
Condition: an event with a caption track published to the engage player. No non-default flag required.

#### Root cause

The captions canvas appends each cue to `_captionsContainer.innerHTML` in the bundled paella-core (served at `/paellaN/ui/paella-player.js` / the 20.x core chunk), so markup in a cue becomes live DOM. The caption entry is built from any media package element whose flavor matches `captions/*` at `modules/engage-paella-player-7/src/js/EpisodeConversor.js:392`, and `/search/episode.json` serves the manifest and the caption file to anonymous clients. The WebVTT and DFXP plugins that consume it are enabled by default at `etc/ui-config/mh_default_org/paella7/config.json:571` and `:574`. The cue text is not HTML-escaped before assignment to `innerHTML`, and `partial-process-uploaded-captions` only cuts and tags the file, it never sanitizes it. Opencast sets neither a Content-Security-Policy nor an X-Content-Type-Options header, so the injected script runs without restriction.

#### Reproduction

Default config, default caption plugins enabled, a non-admin user with `ROLE_API_EVENTS_CREATE`, `ROLE_API_EVENTS_TRACK_EDIT`, and `ROLE_UI_TASKS_CREATE` (no `ROLE_ADMIN`).

1. As the non-admin user, create an event, then upload a WebVTT subtitle as `captions/source` whose cue text is an XSS payload, and publish the event.

```
WEBVTT

00:00:00.000 --> 00:00:30.000
<img src=x onerror=document.title=window.__xss=document.domain>
```

2. The anonymous search manifest then exposes the caption and serves the cue raw.

```
GET /search/episode.json?id=<event>
"type":"captions/source", "url":".../static/.../x.vtt"
GET .../static/.../x.vtt  ->  cue text returned verbatim
```

3. Open the event in the player as an anonymous viewer, open the captions menu, and select the track; the cue is written to `innerHTML` and the `onerror` handler runs.

Live-verified: on Opencast 18.8 (build 8705223) in Chrome, a non-admin author published the subtitle and an anonymous viewer enabled captions, rendering the cue as a live `<img>` node and setting `window.__xss` and `document.title` to `document.domain`. On Opencast 20.0 (build d919405, Paella 8), the served core bundle contains the identical `innerHTML += cue` sink and the player loads the cue raw; the sink executes JavaScript in the engage origin when fed the player's own published caption file.

#### Impact

- JavaScript execution in the Opencast origin in the session of any viewer who enables captions on the event.
- Anonymous viewers and authenticated staff are equally affected; an instructor or admin viewer exposes that session context to the script.
- Session and CSRF-token theft, actions performed as the victim against the Opencast REST API.
- Stored by a non-admin content author, triggered by viewing with captions on, no attacker authentication at view time.

#### Credit

Jan Kahmen, [turingpoint](https://www.turingpoint.de) (jan@turingpoint.de)

## Affected packages

| Package | Ecosystem | PURL | Vulnerable versions | Fixed in |
| --- | --- | --- | --- | --- |
| org.opencastproject:opencast-engage-paella-player-7 | Maven | `pkg:maven/org.opencastproject/opencast-engage-paella-player-7` | < 19.7; >= 20.0, < 20.2 | 19.7, 20.2 |
| paella-core | npm | `pkg:npm/paella-core` | < 1.50.6 | 1.50.6 |

## Fix commits

- Patch: https://github.com/opencast/opencast/commit/701682c635f668228c3e8fb7b4564b3294788e40
- Patch: https://github.com/polimediaupv/paella-core/commit/94a36490808ac5a1f60a0745d71ec9253f6d206b
- Patch: https://github.com/polimediaupv/paella-core/commit/9b2f14ec4cf55efaf4c045c77a5ed8f5ec559ab4
- Patch: https://github.com/polimediaupv/paella-player/commit/6fe4af7306044198c8e91e2e7f4128428b83cf03
- Pull request: https://github.com/opencast/opencast/pull/7736

## Questions

**What is the GHSA ID for CVE-2026-77615?**

GitHub tracks CVE-2026-77615 as GHSA-m6c8-jcw2-5r25.

**What is the CVSS score of CVE-2026-77615?**

CVE-2026-77615 has a CVSS 3.1 base score of 8.7 (High), vector CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N.

**Which versions are affected by CVE-2026-77615?**

org.opencastproject:opencast-engage-paella-player-7 (Maven): < 19.7; >= 20.0, < 20.2. paella-core (npm): < 1.50.6.

**How do I fix CVE-2026-77615?**

Upgrade each package to its fixed version: org.opencastproject:opencast-engage-paella-player-7 19.7 or 20.2; paella-core 1.50.6. The fix is in commits 701682c, 94a3649 and 9b2f14e and pull request #7736.

**What is the Package URL (PURL) for CVE-2026-77615?**

The affected packages' PURLs are pkg:maven/org.opencastproject/opencast-engage-paella-player-7 and pkg:npm/paella-core.

**Is CVE-2026-77615 exploited in the wild?**

CISA's Known Exploited Vulnerabilities catalog doesn't list CVE-2026-77615. That means no confirmed exploitation has been catalogued, not that it is safe to leave unpatched. EPSS estimates a 0.39% chance of exploitation in the next 30 days, higher than 33% of CVEs.

## References

- https://github.com/opencast/opencast/pull/7736 (fix)
- https://github.com/opencast/opencast/commit/701682c635f668228c3e8fb7b4564b3294788e40 (fix)
- https://github.com/polimediaupv/paella-core/commit/94a36490808ac5a1f60a0745d71ec9253f6d206b (fix)
- https://github.com/polimediaupv/paella-core/commit/9b2f14ec4cf55efaf4c045c77a5ed8f5ec559ab4 (fix)
- https://github.com/polimediaupv/paella-player/commit/6fe4af7306044198c8e91e2e7f4128428b83cf03 (fix)
- https://github.com/opencast/opencast/security/advisories/GHSA-m6c8-jcw2-5r25 (advisory)
- https://nvd.nist.gov/vuln/detail/CVE-2026-77615 (advisory)
- https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/77xxx/CVE-2026-77615.json (advisory)
- https://github.com/advisories/GHSA-m6c8-jcw2-5r25 (advisory)
- https://github.com/opencast/opencast (package)
- https://github.com/opencast/opencast/releases/tag/19.7
- https://github.com/opencast/opencast/releases/tag/20.2
- https://github.com/polimediaupv/paella-player/blob/a1b6c42467938a00a4b4d0b8c68435cd4f9d2a16/repos/paella-core/CHANGELOG.md?plain=1#L21

---

Source: CVEminer (https://cveminer.com/vulnerabilities/CVE-2026-77615), compiled from OSV (GitHub advisory), GitHub Advisory Database, OSV (CVE record), CVE.org, CISA KEV catalog. OSV and GitHub Advisory Database content is licensed CC BY 4.0.
