VYPR
High severityNVD Advisory· Published Sep 25, 2025· Updated Sep 25, 2025

Star Citizen EmbedVideo Extension Stored XSS through wikitext caused by usage of non-reserved data attributes

CVE-2025-59839

Description

The EmbedVideo Extension is a MediaWiki extension which adds a parser function called #ev and various parser tags for embedding video clips from various video sharing services. In versions 4.0.0 and prior, the EmbedVideo extension allows adding arbitrary attributes to an HTML element, allowing for stored XSS through wikitext. This issue has been patched via commit 4e075d3.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Stored XSS in MediaWiki EmbedVideo extension via arbitrary HTML attribute injection through wikitext, patched in commit 4e075d3.

Vulnerability

The EmbedVideo extension for MediaWiki allows embedding videos via parser functions and tags like `. In versions 4.0.0 and prior, the extension did not sanitize or restrict the data-iframeconfig` attribute, allowing users to inject arbitrary HTML attributes through wikitext. This enabled stored cross-site scripting (XSS) attacks, as malicious attributes could be processed by the extension's JavaScript code.

Exploitation

An attacker with the ability to edit wikitext can craft a malicious ` or similar tag with a crafted data-iframeconfig` attribute containing JavaScript event handlers or other payloads. When a viewer loads the page, the injected attribute is rendered as part of the HTML, allowing the script to execute in the context of the user's browser session. No special privileges beyond editing rights are required [1][3].

Impact

Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of any user viewing the malicious page. This can lead to session hijacking, defacement, or theft of sensitive information, depending on the wiki's configuration and user permissions.

Mitigation

The issue has been patched in commit 4e075d3 [2], which renames the custom data attribute from data-iframeconfig to data-mw-iframeconfig. This prevents attribute injection because the extension no longer blindly processes user-supplied data-* attributes; it now only uses the prefixed version. Users should update to a version containing this commit or apply the patch manually. No known workarounds are available.

AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
starcitizenwiki/embedvideoPackagist
<= 4.0.0

Affected products

2

Patches

1
4e075d3dc9a1

Merge commit from fork

6 files changed · +15 15
  • includes/EmbedService/EmbedHtmlFormatter.php+1 1 modified
    @@ -101,7 +101,7 @@ public static function toHtml( AbstractEmbedService $service, array $config = []
     				->get( 'EmbedVideoRequireConsent' );
     			if ( $consent === true ) {
     				$iframeConfig = sprintf(
    -					"data-iframeconfig='%s'",
    +					"data-mw-iframeconfig='%s'",
     					$service->getIframeConfig( $width, $height )
     				);
     			}
    
  • includes/EmbedVideo.php+1 1 modified
    @@ -183,7 +183,7 @@ public static function parseEVL( Parser $parser, PPFrame $frame, array $args ):
     		}
     
     		$linkConfig = [
    -			'data-iframeconfig' => $ev->service->getIframeConfig( $ev->args['width'], $ev->args['height'] ),
    +			'data-mw-iframeconfig' => $ev->service->getIframeConfig( $ev->args['width'], $ev->args['height'] ),
     			'data-service' => $ev->args['service'],
     			'data-player' => $ev->args['player'] ?? 'default',
     			'class' => 'embedvideo-evl vplink',
    
  • resources/ext.embedVideo.videolink.js+4 4 modified
    @@ -7,7 +7,7 @@ const {makeIframe, fetchThumb} = require('./iframe.js');
                     e.preventDefault();
     
                     const player = evl.dataset?.player ?? 'default';
    -                const iframeConfig = JSON.parse(evl.dataset?.iframeconfig ?? '{}');
    +                const iframeConfig = JSON.parse(evl.dataset?.mwIframeconfig ?? '{}');
     
                     const playerContainer = document.querySelector(`.embedvideo.evlplayer-${player}`);
                     const iframe = playerContainer.querySelector('iframe');
    @@ -25,7 +25,7 @@ const {makeIframe, fetchThumb} = require('./iframe.js');
                     // No iframe exists, only when explicit consent is required
                     const div = document.querySelector(`.embedvideo.evlplayer-${player}`);
     
    -                if (div === null || evl.dataset?.iframeconfig === null) {
    +                if (div === null || evl.dataset?.mwIframeconfig === null) {
                         console.warn(`No player with id '${player}' found!.`);
                         return;
                     }
    @@ -35,7 +35,7 @@ const {makeIframe, fetchThumb} = require('./iframe.js');
     
                     const origService = div.dataset?.service;
     
    -                div.dataset.iframeconfig = evl.dataset.iframeconfig;
    +                div.dataset.mwIframeconfig = evl.dataset.mwIframeconfig;
                     div.dataset.service = evl.dataset.service;
     
                     const serviceMessage = mw.message('embedvideo-service-' + (evl.dataset?.service ?? 'youtube')).escaped();
    @@ -46,7 +46,7 @@ const {makeIframe, fetchThumb} = require('./iframe.js');
     
                     if (evl.dataset?.privacyUrl !== null) {
                         const link = document.createElement('a');
    -                    link.href = evl.dataset.privacyUrl;
    +					link.href = evl.dataset.privacyUrl;
                         link.rel = 'nofollow,noopener';
                         link.target = '_blank';
                         link.classList.add('embedvideo-privacyNotice__link');
    
  • resources/modules/iframe.js+4 4 modified
    @@ -82,7 +82,7 @@ const fetchThumb = async (url, parent, container) => {
                     title.classList.add('embedvideo-loader__title');
     
                     link.classList.add('embedvideo-loader__link');
    -                link.href = JSON.parse(container?.dataset?.iframeconfig ?? '{"src": "#"}').src;
    +                link.href = JSON.parse(container?.dataset?.mwIframeconfig ?? '{"src": "#"}').src;
                     link.target = '_blank';
                     link.rel = 'noopener noreferrer nofollow';
                     link.innerText = json.title;
    @@ -126,7 +126,7 @@ const makeIframe = function(ev) {
         const wrapper = ev.querySelector('.embedvideo-wrapper');
     
         const getIframeConfig = function() {
    -        let iframeConfig = ev.dataset.iframeconfig;
    +        let iframeConfig = ev.dataset.mwIframeconfig;
     
             iframeConfig = {
                 ...mw.config.get('ev-' + ev.dataset.service + '-config') ?? [],
    @@ -176,7 +176,7 @@ const makeIframe = function(ev) {
     
         /** @type HTMLDivElement|null */
         const consentDiv = wrapper.querySelector('.embedvideo-consent');
    -    let iframeConfig = ev.dataset.iframeconfig;
    +    let iframeConfig = ev.dataset.mwIframeconfig;
     
         if (consentDiv === null || iframeConfig === null) {
             return;
    @@ -206,4 +206,4 @@ const makeIframe = function(ev) {
         }
     }
     
    -module.exports = { makeIframe, fetchThumb }
    \ No newline at end of file
    +module.exports = { makeIframe, fetchThumb }
    
  • tests/phpunit/EmbedService/EmbedHtmlFormatterTest.php+1 1 modified
    @@ -44,7 +44,7 @@ public function testToHtmlConsent() {
     		$service = EmbedServiceFactory::newFromName( 'archiveorg', 'foo' );
     
             // phpcs:ignore Generic.Files.LineLength.TooLong
    -		$this->assertStringContainsString( 'data-iframeconfig=\'{"src":"//archive.org/embed/foo"}\'', EmbedHtmlFormatter::toHtml( $service ) );
    +		$this->assertStringContainsString( 'data-mw-iframeconfig=\'{"src":"//archive.org/embed/foo"}\'', EmbedHtmlFormatter::toHtml( $service ) );
     		$this->assertStringNotContainsString( '<iframe', EmbedHtmlFormatter::toHtml( $service ) );
     	}
     
    
  • tests/phpunit/EmbedVideoTest.php+4 4 modified
    @@ -136,7 +136,7 @@ public function testParseEVYouTubeValid() {
     		$this->assertIsArray( $output );
     		$this->assertCount( 3, $output );
             // phpcs:ignore Generic.Files.LineLength.TooLong
    -		$this->assertStringContainsString( '<figure class="embedvideo" data-service="youtube" data-iframeconfig=\'{"src":"https://www.youtube-nocookie.com/embed/foobar?autoplay=1"}\' style="width:640px">', $output[0] );
    +		$this->assertStringContainsString( '<figure class="embedvideo" data-service="youtube" data-mw-iframeconfig=\'{"src":"https://www.youtube-nocookie.com/embed/foobar?autoplay=1"}\' style="width:640px">', $output[0] );
     	}
     
     	/**
    @@ -308,7 +308,7 @@ public function testParseEVTagIdInInput() {
     		$this->assertIsArray( $output );
     		$this->assertCount( 3, $output );
     		$this->assertStringContainsString(
    -			'<figure class="embedvideo" data-service="youtube" data-iframeconfig',
    +			'<figure class="embedvideo" data-service="youtube" data-mw-iframeconfig',
     			$output[0]
     		);
     	}
    @@ -339,7 +339,7 @@ public function testParseArgsExample1() {
     		$this->assertIsArray( $output );
     		$this->assertCount( 3, $output );
     		$this->assertStringContainsString(
    -			'<figure class="embedvideo" data-service="youtube" data-iframeconfig',
    +			'<figure class="embedvideo" data-service="youtube" data-mw-iframeconfig',
     			$output[0]
     		);
     	}
    @@ -438,7 +438,7 @@ public function testParseEVLYouTube() {
     		$this->assertIsArray( $output );
     		$this->assertCount( 3, $output );
             // phpcs:ignore Generic.Files.LineLength.TooLong
    -		$this->assertStringContainsString( '<a data-iframeconfig="', $output[0] );
    +		$this->assertStringContainsString( '<a data-mw-iframeconfig="', $output[0] );
     		$this->assertStringContainsString( 'Test Text', $output[0] );
     	}
     
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.