VYPR
High severity8.1OSV Advisory· Published Jul 15, 2024· Updated Apr 15, 2026

CVE-2024-40631

CVE-2024-40631

Description

CVE-2024-40631 is an XSS vulnerability in Plate's MediaEmbedElement when custom URL parsers allow javascript:, data:, or vbscript: URLs.

AI Insight

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

CVE-2024-40631 is an XSS vulnerability in Plate's MediaEmbedElement when custom URL parsers allow javascript:, data:, or vbscript: URLs.

Vulnerability

Overview

CVE-2024-40631 is a cross-site scripting (XSS) vulnerability in the open-source rich-text editor Plate, specifically within the MediaEmbedElement component. The flaw arises when editors use custom urlParsers passed to the useMediaState hook; if a parser does not adequately sanitize the URL protocol, it can return dangerous schemes like javascript:, data:, or vbscript:. The same risk applies when the url property from useMediaState or the element is consumed directly without sanitisation [1][3]. The default parsers parseTwitterUrl and parseVideoUrl are not affected [3].

Exploitation

Path

An attacker can exploit this by crafting a malicious URL that, when embedded in a MediaEmbedElement, will be passed unsanitised to an ` src attribute. For example, a custom parser that returns { url: "javascript:alert(1)" } would cause script execution in the context of the editor page. This attack requires the editor to be configured with a vulnerable custom parser or code that directly uses the url` property without validation [3]. No special privileges beyond the ability to insert media content are needed.

Impact

Successful exploitation results in XSS, allowing arbitrary JavaScript execution within the affected page. This can lead to data theft, session hijacking, or other malicious actions, depending on the application context [1][3].

Mitigation

The vulnerability is patched in @udecode/plate-media version 36.0.10, which restricts the embed property from useMediaState to only HTTP and HTTPS URLs. The patch also renames url to unsafeUrl to discourage direct use. Users must update to the patched version or, as a workaround, ensure custom parsers explicitly block dangerous protocols and sanitise any direct use of the url property [2][3].

AI Insight generated on May 20, 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
@udecode/plate-medianpm
< 36.0.1036.0.10

Affected products

2

Patches

1
1bc0971774fb

Harden media embed element against XSS

https://github.com/udecode/plateJoe AndersonJul 6, 2024via ghsa
3 files changed · +73 10
  • .changeset/selfish-dogs-buy.md+6 0 added
    @@ -0,0 +1,6 @@
    +---
    +"@udecode/plate-media": patch
    +---
    +
    +- Explicitly prohibit `javascript:` protocol when parsing URLs in `useMediaState`.
    +- In the return value of `useMediaState`, rename `url` to `unsafeUrl` to indicate that it has not been sanitised.
    
  • packages/media/src/media/useMediaState.spec.ts+37 0 added
    @@ -0,0 +1,37 @@
    +import { type EmbedUrlParser, parseMediaUrl } from './useMediaState';
    +
    +describe('parseMediaUrl', () => {
    +  const parsersWithoutFallback: EmbedUrlParser[] = [
    +    (url) => (url.startsWith('a') ? { id: 'A' } : undefined),
    +    (url) => (url.endsWith('b') ? { id: 'B' } : undefined),
    +  ];
    +
    +  const parsersWithFallback: EmbedUrlParser[] = [
    +    ...parsersWithoutFallback,
    +    () => ({ id: 'C' }),
    +  ];
    +
    +  it('returns undefined if no parsers match', () => {
    +    const embed = parseMediaUrl('x', { urlParsers: parsersWithoutFallback });
    +    expect(embed).toBeUndefined();
    +  });
    +
    +  it('uses the first matching parser', () => {
    +    const embed = parseMediaUrl('ab', { urlParsers: parsersWithoutFallback });
    +    expect(embed?.id).toBe('A');
    +  });
    +
    +  it('uses fallback parser if present', () => {
    +    const embed = parseMediaUrl('javascript', {
    +      urlParsers: parsersWithFallback,
    +    });
    +    expect(embed?.id).toBe('C');
    +  });
    +
    +  it('does not allow javascript: URLs', () => {
    +    const embed = parseMediaUrl('javascript:', {
    +      urlParsers: parsersWithFallback,
    +    });
    +    expect(embed).toBeUndefined();
    +  });
    +});
    
  • packages/media/src/media/useMediaState.ts+30 10 modified
    @@ -4,8 +4,9 @@ import { useElement } from '@udecode/plate-common';
     import { useFocused, useReadOnly, useSelected } from 'slate-react';
     
     import type { TMediaElement } from './types';
    +
    +import { ELEMENT_MEDIA_EMBED, VIDEO_PROVIDERS } from '../media-embed';
     import { ELEMENT_VIDEO } from '../video';
    -import { VIDEO_PROVIDERS, ELEMENT_MEDIA_EMBED} from '../media-embed';
     
     export type EmbedUrlData = {
       id?: string;
    @@ -15,6 +16,30 @@ export type EmbedUrlData = {
     
     export type EmbedUrlParser = (url: string) => EmbedUrlData | undefined;
     
    +export const parseMediaUrl = (
    +  url: string,
    +  {
    +    urlParsers,
    +  }: {
    +    urlParsers: EmbedUrlParser[];
    +  }
    +): EmbedUrlData | undefined => {
    +  // Harden against XSS
    +  try {
    +    if (new URL(url).protocol === 'javascript:') {
    +      return undefined;
    +    }
    +  } catch {}
    +
    +  for (const parser of urlParsers) {
    +    const data = parser(url);
    +
    +    if (data) {
    +      return data;
    +    }
    +  }
    +};
    +
     export const useMediaState = ({
       urlParsers,
     }: {
    @@ -28,15 +53,10 @@ export const useMediaState = ({
       const { align = 'left', id, isUpload, name, type, url } = element;
     
       const embed = React.useMemo(() => {
    -    if (!urlParsers || (type !== ELEMENT_VIDEO && type !== ELEMENT_MEDIA_EMBED)) return;
    +    if (!urlParsers || (type !== ELEMENT_VIDEO && type !== ELEMENT_MEDIA_EMBED))
    +      return;
     
    -    for (const parser of urlParsers) {
    -      const data = parser(url);
    -
    -      if (data) {
    -        return data;
    -      }
    -    }
    +    return parseMediaUrl(url, { urlParsers });
         // eslint-disable-next-line react-hooks/exhaustive-deps
       }, [urlParsers, url]);
     
    @@ -56,6 +76,6 @@ export const useMediaState = ({
         name,
         readOnly,
         selected,
    -    url,
    +    unsafeUrl: url,
       };
     };
    

Vulnerability mechanics

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

References

5

News mentions

0

No linked articles in our index yet.