CVE-2024-29203 (TypeScript, published 2024-03-26T14:15Z)
Description: value: TinyMCE is an open source rich text editor. A cross-site scripting (XSS) vulnerability was discovered in TinyMCE’s content insertion code.  This allowed `iframe` elements containing malicious code to execute when inserted into the editor.  These `iframe` elements are restricted in their permissions by same-origin browser protections, but could still trigger operations such as downloading of malicious assets. This vulnerability is fixed in 6.8.1.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/tinymce/tinymce
Files changed:
modules/tinymce/CHANGELOG.md
modules/tinymce/src/core/main/ts/api/OptionTypes.ts
modules/tinymce/src/core/main/ts/api/Options.ts
modules/tinymce/src/core/main/ts/api/html/DomParser.ts
modules/tinymce/src/core/main/ts/content/PrePostProcess.ts
modules/tinymce/src/core/main/ts/html/ParserFilters.ts
modules/tinymce/src/core/main/ts/init/InitContentBody.ts
modules/tinymce/src/core/main/ts/paste/ProcessFilters.ts
modules/tinymce/src/core/test/ts/browser/content/EditorContentTest.ts
modules/tinymce/src/core/test/ts/browser/html/DomParserTest.ts
modules/tinymce/src/core/test/ts/browser/paste/PasteTest.ts
modules/tinymce/src/plugins/media/main/ts/core/Nodes.ts
modules/tinymce/src/plugins/media/test/ts/browser/ContentFormatsTest.ts
modules/tinymce/src/plugins/media/test/ts/browser/LiveEmbedNodeTest.ts
modules/tinymce/src/plugins/media/test/ts/browser/PlaceholderTest.ts
modules/tinymce/src/plugins/quickbars/test/ts/browser/SelectionToolbarTest.ts
Commit message: TINY-10348 & TINY-10349: Add sandbox_iframes and convert_unsafe_embeds options (#9172)

* TINY-10348: Add sandbox_iframes option

* TINY-10349: Add convert_unsafe_embeds option

* TINY-10348 & TINY-10349: Add paste tests

* TINY-10348 & TINY-10349: Add changelog entries

* TINY-10348: Add missing PageBreakPlugin dependency in SelectionToolbarTest

* TINY-10349: Add attribute preservation tests for convert_unsafe_embeds

Vulnerable code:

const hasTextPatternsLookup = (editor: Editor): boolean =>
  editor.options.isSet('text_patterns_lookup');

const getFontStyleValues = (editor: Editor): string[] =>
  Tools.explode(editor.options.get('font_size_style_values'));

const getFontSizeClasses = (editor: Editor): string[] =>
  Tools.explode(editor.options.get('font_size_classes'));

const isEncodingXml = (editor: Editor): boolean =>
  editor.options.get('encoding') === 'xml';

const getAllowedImageFileTypes = (editor: Editor): string[] =>
  Tools.explode(editor.options.get('images_file_types'));



  shouldForceHexColor
import DomParser from '../api/html/DomParser';
const withSerializedContent = <R extends EditorEvent<{ content: string }>>(content: Content, fireEvent: (content: string) => R, sanitize: boolean): R & { content: Content } => {
      const rootNode = DomParser({ validate: false, forced_root_block: false, sanitize }).parse(eventArgs.content, { context: content.name });
    const processedEventArgs = withSerializedContent(content, (content) => Events.fireGetContent(editor, { ...args, content }), Options.shouldSanitizeXss(editor));
    const processedEventArgs = withSerializedContent(args.content, (content) => Events.fireBeforeSetContent(editor, { ...args, content }), Options.shouldSanitizeXss(editor));
import { Arr, Type } from '@ephox/katamari';
  const parser = DomParser({ sanitize: Options.shouldSanitizeXss(editor) }, editor.schema);
            // TINY-9624: Investigate Safari-specific HTML output
      // TODO: TINY-9624 - the iframe innerHTML on safari is `&lt;textarea&gt;` whereas on other browsers
      //       is `<textarea>`. This causes the mXSS cleaner in DOMPurify to run and causes the different assertions below
      frameborder: '0'
import { describe, it } from '@ephox/bedrock-client';
import { Arr, Fun, Obj } from '@ephox/katamari';
import { TinyDom, TinyHooks } from '@ephox/wrap-mcagar';
  const hook = TinyHooks.bddSetupLight<Editor>({
    toolbar: 'media',
    attrs: Record<string, string>,
            ...Obj.map(attrs, (value) => str.is(value))
import { TinyAssertions, TinyHooks, TinyUiActions } from '@ephox/wrap-mcagar';
  const hook = TinyHooks.bddSetupLight<Editor>({
    extended_valid_elements: 'script[src|type]',
  const iframeStructure = ApproxStructure.build((s) => {
    return s.element('body', {
      children: [
        s.element('p', {
          children: [
            s.element('span', {
              children: [
                s.element('iframe', {}),
                s.element('span', {})
              ]
            })
          ]
        }),
        s.theRest()
      ]
    });
  });

      iframeStructure
  }, [ LinkPlugin, QuickbarsPlugin ], true);

Fixed code:
- New `sandbox_iframes` option that controls whether iframe elements will be added a `sandbox=""` attribute to mitigate malicious intent. #TINY-10348
- New `convert_unsafe_embeds` option that controls whether `<object>` and `<embed>` elements will be converted to more restrictive alternatives, namely `<img>` for image MIME types, `<video>` for video MIME types, `<audio>` audio MIME types, or `<iframe>` for other or unspecified MIME types. #TINY-10349
  convert_unsafe_embeds?: boolean;
  sandbox_iframes?: boolean;
  convert_unsafe_embeds: boolean;
  sandbox_iframes: boolean;
  registerOption('sandbox_iframes', {
    processor: 'boolean',
    default: false
  });

  registerOption('convert_unsafe_embeds', {
    processor: 'boolean',
    default: false
  });

const hasTextPatternsLookup = (editor: Editor): boolean => editor.options.isSet('text_patterns_lookup');
const getFontStyleValues = (editor: Editor): string[] => Tools.explode(editor.options.get('font_size_style_values'));
const getFontSizeClasses = (editor: Editor): string[] => Tools.explode(editor.options.get('font_size_classes'));
const isEncodingXml = (editor: Editor): boolean => editor.options.get('encoding') === 'xml';
const getAllowedImageFileTypes = (editor: Editor): string[] => Tools.explode(editor.options.get('images_file_types'));
const shouldSandboxIframes = option('sandbox_iframes');
const shouldConvertUnsafeEmbeds = option('convert_unsafe_embeds');
  shouldForceHexColor,
  shouldSandboxIframes,
  shouldConvertUnsafeEmbeds
  convert_unsafe_embeds?: boolean;
  sandbox_iframes?: boolean;
import DomParser, { DomParserSettings } from '../api/html/DomParser';
const withSerializedContent = <R extends EditorEvent<{ content: string }>>(content: Content, fireEvent: (content: string) => R, parserSettings: DomParserSettings): R & { content: Content } => {
      const rootNode = DomParser({ validate: false, forced_root_block: false, ...parserSettings }).parse(eventArgs.content, { context: content.name });
    const processedEventArgs = withSerializedContent(content, (content) => Events.fireGetContent(editor, { ...args, content }), { sanitize: Options.shouldSanitizeXss(editor), sandbox_iframes: Options.shouldSandboxIframes(editor) });
    const processedEventArgs = withSerializedContent(args.content, (content) => Events.fireBeforeSetContent(editor, { ...args, content }), { sanitize: Options.shouldSanitizeXss(editor), sandbox_iframes: Options.shouldSandboxIframes(editor) });
import { Arr, Strings, Type } from '@ephox/katamari';
const isMimeType = (mime: string, type: 'image' | 'video' | 'audio'): boolean => Strings.startsWith(mime, `${type}/`);

const createSafeEmbed = (mime?: string, src?: string, width?: string, height?: string, sandboxIframes?: boolean): AstNode => {
  let name: 'iframe' | 'img' | 'video' | 'audio';
  if (Type.isUndefined(mime)) {
    name = 'iframe';
  } else if (isMimeType(mime, 'image')) {
    name = 'img';
  } else if (isMimeType(mime, 'video')) {
    name = 'video';
  } else if (isMimeType(mime, 'audio')) {
    name = 'audio';
  } else {
    name = 'iframe';
  }

  const embed = new AstNode(name, 1);
  embed.attr(name === 'audio' ? { src } : { src, width, height });

  // TINY-10349: Show controls for audio and video so the replaced embed is visible in editor.
  if (name === 'audio' || name === 'video') {
    embed.attr('controls', '');
  }

  if (name === 'iframe' && sandboxIframes) {
    embed.attr('sandbox', '');
  }
  return embed;
};


  if (settings.convert_unsafe_embeds) {
    parser.addNodeFilter('object,embed', (nodes) => Arr.each(nodes, (node) => {
      node.replace(
        createSafeEmbed(
          node.attr('type'),
          node.name === 'object' ? node.attr('data') : node.attr('src'),
          node.attr('width'),
          node.attr('height'),
          settings.sandbox_iframes
        ));
    }));
  }

  if (settings.sandbox_iframes) {
    parser.addNodeFilter('iframe', (nodes) => Arr.each(nodes, (node) => node.attr('sandbox', '')));
  }
    convert_unsafe_embeds: getOption('convert_unsafe_embeds'),
    sandbox_iframes: getOption('sandbox_iframes'),
  const parser = DomParser({ sanitize: Options.shouldSanitizeXss(editor), sandbox_iframes: Options.shouldSandboxIframes(editor) }, editor.schema);

        context('iframe sandboxing', () => {
          context('sandbox_iframes default', () => {
            const hook = TinyHooks.bddSetupLight<Editor>({
              ...options,
              base_url: '/project/tinymce/js/tinymce'
            }, []);

            it('TINY-10348: Iframe should not be sandboxed by default', () => {
              const editor = hook.editor();
              editor.setContent('<iframe src="about:blank"></iframe>');
              TinyAssertions.assertContent(editor, '<p><iframe src="about:blank"></iframe></p>');
            });
          });

          context('sandbox_iframes: false', () => {
            const hook = TinyHooks.bddSetupLight<Editor>({
              ...options,
              base_url: '/project/tinymce/js/tinymce',
              sandbox_iframes: false
            }, []);

            it('TINY-10348: Iframe should not be sandboxed when sandbox_iframes: false', () => {
              const editor = hook.editor();
              editor.setContent('<iframe src="about:blank"></iframe>');
              TinyAssertions.assertContent(editor, '<p><iframe src="about:blank"></iframe></p>');
            });

            it('TINY-10348: Iframe with sandbox attribute should not be modified when sandbox_iframes: false', () => {
              const editor = hook.editor();
              editor.setContent('<iframe src="about:blank" sandbox="allow-scripts allow-same-origin allow-forms"></iframe>');
              TinyAssertions.assertContent(editor, '<p><iframe src="about:blank" sandbox="allow-scripts allow-same-origin allow-forms"></iframe></p>');
            });
          });

          context('sandbox_iframes: true', () => {
            const hook = TinyHooks.bddSetupLight<Editor>({
              ...options,
              base_url: '/project/tinymce/js/tinymce',
              sandbox_iframes: true
            }, []);

            it('TINY-10348: Iframe should be sandboxed when sandbox_iframes: true', () => {
              const editor = hook.editor();
              editor.setContent('<iframe src="about:blank"></iframe>');
              TinyAssertions.assertContent(editor, '<p><iframe src="about:blank" sandbox=""></iframe></p>');
            });

            it('TINY-10348: Iframe with sandbox attribute should be sandboxed when sandbox_iframes: true', () => {
              const editor = hook.editor();
              editor.setContent('<iframe src="about:blank" sandbox="allow-scripts allow-same-origin allow-forms"></iframe>');
              TinyAssertions.assertContent(editor, '<p><iframe src="about:blank" sandbox=""></iframe></p>');
            });
          });
        });

        context('Convert unsafe embeds', () => {
          const setAndAssertEmbed = (editor: Editor, embedHtml: string, expectedHtml: string) => {
            editor.setContent(embedHtml);
            TinyAssertions.assertContent(editor, `<p>${expectedHtml}</p>`);
          };

          const testNoConversion = (hook: TinyHooks.Hook<Editor>, embedHtml: string) => () => {
            const editor = hook.editor();
            setAndAssertEmbed(editor, embedHtml, embedHtml);
          };

          const testConversion = (hook: TinyHooks.Hook<Editor>, embedHtml: string, expectedHtml: string) => () => {
            const editor = hook.editor();
            setAndAssertEmbed(editor, embedHtml, expectedHtml);
          };

          context('convert_unsafe_embeds default', () => {
            const hook = TinyHooks.bddSetupLight<Editor>({
              ...options,
              base_url: '/project/tinymce/js/tinymce'
            }, []);

            it('TINY-10349: Object elements should not be converted', testNoConversion(hook, '<object data="about:blank"></object>'));
            it('TINY-10349: Embed elements should not be converted', testNoConversion(hook, '<embed src="about:blank">'));
          });

          context('convert_unsafe_embeds: false', () => {
            const hook = TinyHooks.bddSetupLight<Editor>({
              ...options,
              base_url: '/project/tinymce/js/tinymce',
              convert_unsafe_embeds: false
            }, []);

            it('TINY-10349: Object elements should not be converted', testNoConversion(hook, '<object data="about:blank"></object>'));
            it('TINY-10349: Embed elements should not be converted', testNoConversion(hook, '<embed src="about:blank">'));
          });

          context('convert_unsafe_embeds: true', () => {
            const hook = TinyHooks.bddSetupLight<Editor>({
              ...options,
              base_url: '/project/tinymce/js/tinymce',
              convert_unsafe_embeds: true
            }, []);

            it('TINY-10349: Object elements should be converted to iframe',
              testConversion(hook, '<object data="about:blank"></object>', '<iframe src="about:blank"></iframe>'));

            it('TINY-10349: Embed elements should be converted to iframe',
              testConversion(hook, '<embed src="about:blank">', '<iframe src="about:blank"></iframe>'));
          });

          context('convert_unsafe_embeds: true, sandbox_iframes: true', () => {
            const hook = TinyHooks.bddSetupLight<Editor>({
              ...options,
              base_url: '/project/tinymce/js/tinymce',
              convert_unsafe_embeds: true,
              sandbox_iframes: true
            }, []);

            it('TINY-10349: Object elements should be converted to sandboxed iframe',
              testConversion(hook, '<object data="about:blank"></object>', '<iframe src="about:blank" sandbox=""></iframe>'));

            it('TINY-10349: Embed elements should be converted to sandboxed iframe',
              testConversion(hook, '<embed src="about:blank">', '<iframe src="about:blank" sandbox=""></iframe>'));
          });
        });
            // TINY-9624: Safari seems to encode the contents of iframes
      // TINY-9624: Safari encodes the iframe innerHTML is `&lt;textarea&gt;`. On Chrome and Firefox, the innerHTML is `<textarea>`, causing
      // the mXSS cleaner in DOMPurify to run and remove the iframe.

      context('Sandboxing iframes', () => {
        const serializeIframeHtml = (sandbox: boolean): string => {
          const parser = DomParser({ ...scenario.settings, sandbox_iframes: sandbox });
          return serializer.serialize(parser.parse('<iframe src="about:blank"></iframe>'));
        };

        it('TINY-10348: iframes should be sandboxed when sandbox_iframes: false', () =>
          assert.equal(serializeIframeHtml(false), '<iframe src="about:blank"></iframe>'));

        it('TINY-10348: iframes should be sandboxed when sandbox_iframes: true', () =>
          assert.equal(serializeIframeHtml(true), '<iframe src="about:blank" sandbox=""></iframe>'));
      });

      context('Convert unsafe embeds', () => {
        const serializeEmbedHtml = (embedHtml: string, convert: boolean): string => {
          const parser = DomParser({ ...scenario.settings, convert_unsafe_embeds: convert });
          return serializer.serialize(parser.parse(embedHtml));
        };

        const testConversion = (embedHtml: string, expectedHtml: string) => () => {
          const serializedHtml = serializeEmbedHtml(embedHtml, true);
          assert.equal(serializedHtml, expectedHtml);
        };

        context('convert_unsafe_embeds: false', () => {
          const testNoConversion = (embedHtml: string) => () => {
            const serializedHtml = serializeEmbedHtml(embedHtml, false);
            assert.equal(serializedHtml, embedHtml);
          };

          it('TINY-10349: Object elements should not be converted', testNoConversion('<object data="about:blank"></object>'));
          it('TINY-10349: Object elements with a mime type should not be converted', testNoConversion('<object data="about:blank" type="image/png"></object>'));
          it('TINY-10349: Embed elements should notr be converted', testNoConversion('<embed src="about:blank">'));
          it('TINY-10349: Embed elements with a mime type should not be converted', testNoConversion('<embed src="about:blank" type="image/png">'));
        });

        context('convert_unsafe_embeds: true', () => {
          it('TINY-10349: Object elements without a mime type should be converted to iframe',
            testConversion('<object data="about:blank"></object>', '<iframe src="about:blank"></iframe>'));
          it('TINY-10349: Object elements with an image mime type should be converted to img',
            testConversion('<object data="about:blank" type="image/png"></object>', '<img src="about:blank">'));
          it('TINY-10349: Object elements with a video mime type should be converted to video',
            testConversion('<object data="about:blank" type="video/mp4"></object>', '<video src="about:blank" controls=""></video>'));
          it('TINY-10349: Object elements with an audio mime type should be converted to audio',
            testConversion('<object data="about:blank" type="audio/mpeg"></object>', '<audio src="about:blank" controls=""></audio>'));
          it('TINY-10349: Object elements with other mime type should be converted to iframe',
            testConversion('<object data="about:blank" type="application/pdf"></object>', '<iframe src="about:blank"></iframe>'));

          it('TINY-10349: Embed elements without a mime type should be converted to iframe',
            testConversion('<embed src="about:blank">', '<iframe src="about:blank"></iframe>'));
          it('TINY-10349: Embed elements with an image mime type should be converted to img',
            testConversion('<embed src="about:blank" type="image/png">', '<img src="about:blank">'));
          it('TINY-10349: Embed elements with a video mime type should be converted to video',
            testConversion('<embed src="about:blank" type="video/mp4">', '<video src="about:blank" controls=""></video>'));
          it('TINY-10349: Embed elements with an audio mime type should be converted to audio',
            testConversion('<embed src="about:blank" type="audio/mpeg">', '<audio src="about:blank" controls=""></audio>'));
          it('TINY-10349: Embed elements with other mime type should be converted to iframe',
            testConversion('<embed src="about:blank" type="application/pdf">', '<iframe src="about:blank"></iframe>'));
        });

        context('convert_unsafe_embeds: true, sandbox_iframes: true', () => {
          const testSandboxedConversion = (embedHtml: string, expectedHtml: string) => () => {
            const parser = DomParser({ ...scenario.settings, convert_unsafe_embeds: true, sandbox_iframes: true });
            const serializedHtml = serializer.serialize(parser.parse(embedHtml));
            assert.equal(serializedHtml, expectedHtml);
          };

          it('TINY-10349: Object elements without a mime type should be converted to sandboxed iframe',
            testSandboxedConversion('<object data="about:blank"></object>', '<iframe src="about:blank" sandbox=""></iframe>'));

          it('TINY-10349: Embed elements without a mime type should be converted to sandboxed iframe',
            testSandboxedConversion('<embed src="about:blank">', '<iframe src="about:blank" sandbox=""></iframe>'));
        });

        context('convert_unsafe_embeds: true, attribute preservation', () => {
          it('TINY-10349: Object elements should perserve width and height attributes only',
            testConversion('<object data="about:blank" width="100" height="100" style="color: red;"></object>', '<iframe src="about:blank" width="100" height="100"></iframe>'));
          it('TINY-10349: Object elements with an image mime type should perserve width and height attributes only',
            testConversion('<object data="about:blank" type="image/png" width="100" height="100" style="color: red;"></object>', '<img src="about:blank" width="100" height="100">'));
          it('TINY-10349: Object elements with a video mime type should perserve width and height attributes only',
            testConversion('<object data="about:blank" type="video/mp4" width="100" height="100" style="color: red;"></object>', '<video src="about:blank" width="100" height="100" controls=""></video>'));
          it('TINY-10349: Object elements with an audio mime type should not perserve other attributes only',
            testConversion('<object data="about:blank" type="audio/mpeg" width="100" height="100" style="color: red;"></object>', '<audio src="about:blank" controls=""></audio>'));
          it('TINY-10349: Object elements with other mime type should perserve width and height attributes only',
            testConversion('<object data="about:blank" type="application/pdf" width="100" height="100" style="color: red;"></object>', '<iframe src="about:blank" width="100" height="100"></iframe>'));

          it('TINY-10349: Embed elements should preserve width and heigth attributes only',
            testConversion('<embed src="about:blank" width="100" height="100" style="color: red;">', '<iframe src="about:blank" width="100" height="100"></iframe>'));
          it('TINY-10349: Embed elements with an image mime type should preserve width and height attributes only',
            testConversion('<embed src="about:blank" type="image/png" width="100" height="100" style="color: red;">', '<img src="about:blank" width="100" height="100">'));
          it('TINY-10349: Embed elements with a video mime type should preserve width and height attributes only',
            testConversion('<embed src="about:blank" type="video/mp4" width="100" height="100" style="color: red;">', '<video src="about:blank" width="100" height="100" controls=""></video>'));
          it('TINY-10349: Embed elements with an audio mime type should not preserve other attributes',
            testConversion('<embed src="about:blank" type="audio/mpeg" width="100" height="100" style="color: red;">', '<audio src="about:blank" controls=""></audio>'));
          it('TINY-10349: Embed elements with other mime type should preserve width and height attributes only',
            testConversion('<embed src="about:blank" type="application/pdf" width="100" height="100" style="color: red;">', '<iframe src="about:blank" width="100" height="100"></iframe>'));
        });
      });
  const testPasteHtml = (hook: TinyHooks.Hook<Editor>, html: string, expected: string) => () => {
    const editor = hook.editor();
    editor.setContent('');
    editor.execCommand('mceInsertClipboardContent', false, { html });
    TinyAssertions.assertContent(editor, expected);
  };


    context('iframe sandboxing', () => {
      context('sandbox_iframes: false', () => {
        const hook = TinyHooks.bddSetupLight<Editor>({
          base_url: '/project/tinymce/js/tinymce',
          sandbox_iframes: false
        });

        it('TINY-10348: Pasted iframe should not be sandboxed',
          testPasteHtml(hook, '<iframe src="about:blank"></iframe>', '<p><iframe src="about:blank"></iframe></p>'));
      });

      context('sandbox_iframes: true', () => {
        const hook = TinyHooks.bddSetupLight<Editor>({
          base_url: '/project/tinymce/js/tinymce',
          sandbox_iframes: true
        });

        it('TINY-10348: Pasted iframe should be sandboxed',
          testPasteHtml(hook, '<iframe src="about:blank"></iframe>', '<p><iframe src="about:blank" sandbox=""></iframe></p>'));
      });
    });

    context('Convert unsafe embeds', () => {
      context('convert_unsafe_embeds: false', () => {
        const hook = TinyHooks.bddSetupLight<Editor>({
          base_url: '/project/tinymce/js/tinymce',
          convert_unsafe_embeds: false
        });

        it('TINY-10349: Pasted object element should not be converted',
          testPasteHtml(hook, '<object data="about:blank"></object>', '<p><object data="about:blank"></object></p>'));

        it('TINY-10349: Pasted embed element should not be converted',
          testPasteHtml(hook, '<embed src="about:blank">', '<p><embed src="about:blank"></p>'));
      });

      context('convert_unsafe_embeds: true', () => {
        const hook = TinyHooks.bddSetupLight<Editor>({
          base_url: '/project/tinymce/js/tinymce',
          convert_unsafe_embeds: true
        });

        it('TINY-10349: Pasted object element should be converted to iframe',
          testPasteHtml(hook, '<object data="about:blank">', '<p><iframe src="about:blank"></iframe></p>'));

        it('TINY-10349: Pasted embed element should be converted to iframe',
          testPasteHtml(hook, '<embed src="about:blank">', '<p><iframe src="about:blank"></iframe></p>'));
      });

      context('convert_unsafe_embeds: true, sandbox_iframes: true', () => {
        const hook = TinyHooks.bddSetupLight<Editor>({
          base_url: '/project/tinymce/js/tinymce',
          convert_unsafe_embeds: true,
          sandbox_iframes: true
        });

        it('TINY-10349: Pasted object element should be converted to sandboxed iframe',
          testPasteHtml(hook, '<object data="about:blank"></object>', '<p><iframe src="about:blank" sandbox=""></iframe></p>'));

        it('TINY-10349: Pasted embed element should be converted to sandboxed iframe',
          testPasteHtml(hook, '<embed src="about:blank">', '<p><iframe src="about:blank" sandbox=""></iframe></p>'));
      });
    });
      frameborder: '0',
      sandbox: node.attr('sandbox')
  it('TINY-10348: Iframe retained as is with sandbox_iframes: false', async () => {
    const editor = await McEditor.pFromSettings({ ...settings, sandbox_iframes: false });
    editor.setContent('<iframe src="320x240.ogg" allowfullscreen></iframe>');
    TinyAssertions.assertContent(editor,
      '<p><iframe src="320x240.ogg" width="300" height="150" allowfullscreen="allowfullscreen"></iframe></p>'
    );
    McEditor.remove(editor);
  });

  it('TINY-10348: Iframe retained as is with sandbox_iframes: true', async () => {
    const editor = await McEditor.pFromSettings({ ...settings, sandbox_iframes: true });
    editor.setContent('<iframe src="320x240.ogg" allowfullscreen></iframe>');
    TinyAssertions.assertContent(editor,
      '<p><iframe src="320x240.ogg" width="300" height="150" sandbox="" allowfullscreen="allowfullscreen"></iframe></p>'
    );
    McEditor.remove(editor);
  });

import { context, describe, it } from '@ephox/bedrock-client';
import { Arr, Fun, Obj, Type } from '@ephox/katamari';
import { McEditor, TinyDom, TinyHooks } from '@ephox/wrap-mcagar';
  const baseSettings = {
    toolbar: 'media'
  };
  const hook = TinyHooks.bddSetupLight<Editor>({
    ...baseSettings,
    attrs: Record<string, string | null>,
            ...Obj.map(attrs, (value) => Type.isNull(value) ? str.none() : str.is(value))

  context('Sandboxing iframes', () => {
    const initialIframeHtml = '<iframe src="about:blank"></iframe>';

    it('TINY-10348: sandbox_iframes: false should not have sandbox attribute in live embed', async () => {
      const editor = await McEditor.pFromSettings<Editor>({ ...baseSettings, sandbox_iframes: false });
      editor.setContent(initialIframeHtml);
      assertStructure(editor, 'iframe', [ ], { sandbox: null }, { });
    });

    it('TINY-10348: sandbox_iframes: true should havce sandbox attribute in live embed', async () => {
      const editor = await McEditor.pFromSettings<Editor>({ ...baseSettings, sandbox_iframes: true });
      editor.setContent(initialIframeHtml);
      assertStructure(editor, 'iframe', [ ], { sandbox: '' }, { });
    });
  });
import { McEditor, TinyAssertions, TinyHooks, TinyUiActions } from '@ephox/wrap-mcagar';
  const baseSettings = {
    extended_valid_elements: 'script[src|type]'
  };

  const hook = TinyHooks.bddSetupLight<Editor>({
    ...baseSettings,
    const createIframeStructure = (sandbox: boolean) => ApproxStructure.build((s, str) => {
      return s.element('body', {
        children: [
          s.element('p', {
            children: [
              s.element('span', {
                children: [
                  s.element('iframe', {
                    attrs: {
                      sandbox: sandbox ? str.is('') : str.none()
                    }
                  }),
                  s.element('span', {})
                ]
              })
            ]
          }),
          s.theRest()
        ]
      });
    });

      createIframeStructure(false)

    it('TINY-10348: Live iframe embed structure when sandbox_iframes: false', async () => {
      const editor = await McEditor.pFromSettings<Editor>({
        ...baseSettings,
        sandbox_iframes: false
      });
      pTestPlaceholder(editor,
        'https://www.youtube.com/watch?v=P_205ZY52pY',
        '<p><iframe src="https://www.youtube.com/embed/P_205ZY52pY" width="560" ' +
        'height="314" allowfullscreen="allowfullscreen"></iframe></p>',
        createIframeStructure(false)
      );
    });

    it('TINY-10348: Live iframe embed structure when sandbox_iframes: true', async () => {
      const editor = await McEditor.pFromSettings<Editor>({
        ...baseSettings,
        sandbox_iframes: true
      });
      pTestPlaceholder(editor,
        'https://www.youtube.com/watch?v=P_205ZY52pY',
        '<p><iframe src="https://www.youtube.com/embed/P_205ZY52pY" width="560" ' +
        'height="314" sandbox="" allowfullscreen="allowfullscreen"></iframe></p>',
        createIframeStructure(true)
      );
    });
import PageBreakPlugin from 'tinymce/plugins/pagebreak/Plugin';
  }, [ LinkPlugin, QuickbarsPlugin, PageBreakPlugin ], true);