VYPR
Low severityNVD Advisory· Published Aug 29, 2022· Updated Apr 22, 2025

Improper Control of Generation of Code ('Code Injection') in mdx-mermaid

CVE-2022-36036

Description

mdx-mermaid provides plug and play access to Mermaid in MDX. There is a potential for an arbitrary javascript injection in versions less than 1.3.0 and 2.0.0-rc1. Modify any mermaid code blocks with arbitrary code and it will execute when the component is loaded by MDXjs. This vulnerability was patched in version(s) 1.3.0 and 2.0.0-rc2. There are currently no known workarounds.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
mdx-mermaidnpm
< 1.3.01.3.0
mdx-mermaidnpm
>= 2.0.0-rc1, < 2.0.0-rc22.0.0-rc2

Affected products

1

Patches

1
f2b99386660f

Merge pull request from GHSA-rvgm-35jw-q628

https://github.com/sjwall/mdx-mermaidSam WallAug 22, 2022via ghsa
7 files changed · +710 257
  • package.json+2 1 modified
    @@ -53,6 +53,7 @@
         "@rollup/plugin-babel": "^5.3.1",
         "@rollup/plugin-commonjs": "^22.0.2",
         "@rollup/plugin-typescript": "^8.3.4",
    +    "@testing-library/react": "^11.1.0",
         "@types/jest": "^27.4.0",
         "@types/mermaid": "^8.2.7",
         "@types/react": "^17.0.38",
    @@ -69,7 +70,7 @@
         "jest": "^27.4.7",
         "mermaid": "^8.0.0",
         "react": "^17.0.1",
    -    "react-test-renderer": "^17.0.2",
    +    "react-dom": "^17.0.0",
         "rimraf": "^3.0.2",
         "rollup": "^2.78.1",
         "ts-jest": "^27.1.2",
    
  • src/mdxast-mermaid.spec.ts+154 30 modified
    @@ -24,7 +24,24 @@ function createTestCompiler (config?: Config) {
     test('No mermaid', async () => {
       const mdxCompiler = createTestCompiler()
       const result = await mdxCompiler.process('# Heading 1\n\nNo Mermaid diagram :(')
    -  expect(result.contents).toEqual('\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = "wrapper"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">\n    <h1>{`Heading 1`}</h1>\n    <p>{`No Mermaid diagram :(`}</p>\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;')
    +  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
    +
    +
    +const layoutProps = {\n  \n};
    +const MDXLayout = "wrapper"
    +export default function MDXContent({
    +  components,
    +  ...props
    +}) {
    +  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
    +
    +    <h1>{\`Heading 1\`}</h1>
    +    <p>{\`No Mermaid diagram :(\`}</p>
    +    </MDXLayout>;
    +}
    +
    +;
    +MDXContent.isMDXComponent = true;`)
     })
     
     test('Basic', async () => {
    @@ -37,7 +54,26 @@ graph TD;
         B-->D;
         C-->D;
     \`\`\``)
    -  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
    +  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
    +
    +
    +const layoutProps = {\n  \n};
    +const MDXLayout = "wrapper"
    +export default function MDXContent({
    +  components,
    +  ...props
    +}) {
    +  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
    +
    +    <h1>{\`Heading 1\`}</h1>
    +    <Mermaid {...{
    +      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    +    }} mdxType="Mermaid"></Mermaid>
    +    </MDXLayout>;
    +}
    +
    +;
    +MDXContent.isMDXComponent = true;`)
     })
     
     test('Existing import', async () => {
    @@ -50,7 +86,26 @@ graph TD;
         B-->D;
         C-->D;
     \`\`\``)
    -  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
    +  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
    +
    +
    +const layoutProps = {\n  \n};
    +const MDXLayout = "wrapper"
    +export default function MDXContent({
    +  components,
    +  ...props
    +}) {
    +  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
    +
    +    <h1>{\`Heading 1\`}</h1>
    +    <Mermaid {...{
    +      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    +    }} mdxType="Mermaid"></Mermaid>
    +    </MDXLayout>;
    +}
    +
    +;
    +MDXContent.isMDXComponent = true;`)
     })
     
     test('Existing import from ts exports(without /lib)', async () => {
    @@ -63,7 +118,26 @@ graph TD;
         B-->D;
         C-->D;
     \`\`\``)
    -  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
    +  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/Mermaid';
    +
    +
    +const layoutProps = {\n  \n};
    +const MDXLayout = "wrapper"
    +export default function MDXContent({
    +  components,
    +  ...props
    +}) {
    +  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
    +
    +    <h1>{\`Heading 1\`}</h1>
    +    <Mermaid {...{
    +      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    +    }} mdxType="Mermaid"></Mermaid>
    +    </MDXLayout>;
    +}
    +
    +;
    +MDXContent.isMDXComponent = true;`)
     })
     
     test('Other imports', async () => {
    @@ -76,7 +150,28 @@ graph TD;
         B-->D;
         C-->D;
     \`\`\``)
    -  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\nimport { A } from 'a';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
    +  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
    +import { A } from 'a';
    +
    +
    +const layoutProps = {\n  \n};
    +const MDXLayout = "wrapper"
    +export default function MDXContent({
    +  components,
    +  ...props
    +}) {
    +  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
    +
    +
    +    <h1>{\`Heading 1\`}</h1>
    +    <Mermaid {...{
    +      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    +    }} mdxType="Mermaid"></Mermaid>
    +    </MDXLayout>;
    +}
    +
    +;
    +MDXContent.isMDXComponent = true;`)
     })
     
     test('Other imports component', async () => {
    @@ -101,7 +196,7 @@ export default function MDXContent({
     
     
         <h1>{\`Heading 1\`}</h1>
    -    <Mermaid config={{}} chart={\`graph TD;
    +    <Mermaid chart={\`graph TD;
           A-->B;
           A-->C;
           B-->D;
    @@ -144,7 +239,7 @@ export default function MDXContent({
         <h1>{\`Heading 1\`}</h1>
         <A mdxType="A">Hi</A>
         <h2>{\`Heading 2\`}</h2>
    -    <Mermaid config={{}} chart={\`graph TD;
    +    <Mermaid chart={\`graph TD;
           A-->B;
           A-->C;
           B-->D;
    @@ -178,15 +273,10 @@ export default function MDXContent({
       return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
     
         <h1>{\`Heading 1\`}</h1>
    -    <Mermaid config={{
    -      "mermaid": {
    -        "theme": "dark"
    -      }
    -    }} chart={\`graph TD;
    -    A-->B;
    -    A-->C;
    -    B-->D;
    -    C-->D;\`} mdxType="Mermaid" />
    +    <Mermaid {...{
    +      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
    +      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    +    }} mdxType="Mermaid"></Mermaid>
         </MDXLayout>;
     }
     
    @@ -214,11 +304,7 @@ export default function MDXContent({
       return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
     
         <h1>{\`Heading 1\`}</h1>
    -    <Mermaid config={{
    -      "mermaid": {
    -        "theme": "dark"
    -      }
    -    }} chart={\`graph TD;
    +    <Mermaid chart={\`graph TD;
         A-->B;
         A-->C;
         B-->D;
    @@ -257,15 +343,10 @@ export default function MDXContent({
       return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
     
         <h1>{\`Heading 1\`}</h1>
    -    <Mermaid config={{
    -      "mermaid": {
    -        "theme": "dark"
    -      }
    -    }} chart={\`graph TD;
    -    A-->B;
    -    A-->C;
    -    B-->D;
    -    C-->D;\`} mdxType="Mermaid" />
    +    <Mermaid {...{
    +      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
    +      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    +    }} mdxType="Mermaid"></Mermaid>
         <Mermaid chart={\`graph TD;
         E-->F;
         E-->G;
    @@ -277,3 +358,46 @@ export default function MDXContent({
     ;
     MDXContent.isMDXComponent = true;`)
     })
    +
    +test('Multiple code block', async () => {
    +  const mdxCompiler = createTestCompiler({ mermaid: { theme: 'dark' } })
    +  const result = await mdxCompiler.process(`# Heading 1\n
    +\`\`\`mermaid
    +graph TD;
    +    A-->B;
    +    A-->C;
    +    B-->D;
    +    C-->D;
    +\`\`\`
    +\`\`\`mermaid
    +graph TD;
    +E-->F;
    +E-->G;
    +F-->H;
    +G-->H;
    +\`\`\``)
    +  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
    +
    +
    +const layoutProps = {\n  \n};
    +const MDXLayout = "wrapper"
    +export default function MDXContent({
    +  components,
    +  ...props
    +}) {
    +  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
    +
    +    <h1>{\`Heading 1\`}</h1>
    +    <Mermaid {...{
    +      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
    +      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
    +    }} mdxType="Mermaid"></Mermaid>
    +    <Mermaid {...{
    +      "chart": "graph TD;\\nE-->F;\\nE-->G;\\nF-->H;\\nG-->H;"
    +    }} mdxType="Mermaid"></Mermaid>
    +    </MDXLayout>;
    +}
    +
    +;
    +MDXContent.isMDXComponent = true;`)
    +})
    
  • src/mdxast-mermaid.ts+10 18 modified
    @@ -53,28 +53,20 @@ export default function plugin (config?: Config) {
         })
     
         // Replace each Mermaid code block with the Mermaid component
    -    instances.forEach(([node, index, parent]) => {
    +    instances.forEach(([node, index, parent], i) => {
           parent.children.splice(index, 1, {
    -        type: 'jsx',
    -        value: `<Mermaid chart={\`${node.value}\`}/>`,
    -        position: node.position
    +        type: 'mermaidCodeBlock',
    +        data: {
    +          hName: 'Mermaid',
    +          hProperties: {
    +            config: i > 0 ? undefined : JSON.stringify(config),
    +            chart: node.value
    +          }
    +        }
           })
         })
     
    -    // Look for any components
    -    visit<Literal<string> & { type: 'jsx' }>(ast, { type: 'jsx' }, (node, index, parent) => {
    -      if (/.*<Mermaid.*/.test(node.value)) {
    -        // If the component doesn't have config
    -        if (!/.*config={.*/.test(node.value)) {
    -          const index = node.value.indexOf('<Mermaid') + 8
    -          node.value = node.value.substring(0, index) +
    -          ` config={${JSON.stringify(config || {})}}` +
    -            node.value.substring(index)
    -        }
    -        insertImport(ast)
    -        return visit.EXIT
    -      }
    -    })
    +    insertImport(ast)
         return ast
       }
     }
    
  • src/Mermaid.spec.tsx+92 152 modified
    @@ -7,9 +7,8 @@
      * This source code is licensed under the MIT license found in the
      * license file in the root directory of this source tree.
      */
    -import mermaid from 'mermaid'
     import React from 'react'
    -import renderer from 'react-test-renderer'
    +import { act, render, RenderResult } from '@testing-library/react'
     import { Mermaid } from './Mermaid'
     import {
       DARK_THEME_KEY,
    @@ -18,185 +17,126 @@ import {
     } from './theme.helper'
     import * as ThemeHelper from './theme.helper'
     
    -async function waitFor (ms: number) {
    -  return new Promise<void>(resolve => {
    -    setTimeout(() => resolve(), ms)
    -  })
    -}
    -
     jest.mock('mermaid')
     
    +// eslint-disable-next-line import/first
    +import mermaid from 'mermaid'
    +
    +const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
    +
    +const diagram = `graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;`
    +
     afterEach(() => {
       jest.clearAllMocks()
     })
     
    +const removeUniqueness = (element: Element) => {
    +  element.querySelectorAll('style').forEach((v) => v.remove())
    +  element.querySelectorAll('svg').forEach((v) => {
    +    v.removeAttribute('id')
    +    v.parentElement!.removeAttribute('id')
    +  })
    +}
    +
    +const expectMermaidMatch = (result: RenderResult) => {
    +  removeUniqueness(result.baseElement)
    +  expect(result.baseElement.parentElement).toMatchSnapshot()
    +  return result
    +}
    +
     it('renders without diagram', () => {
    -  const component = renderer.create(<Mermaid chart={''} config={{}}  />)
    -  expect(mermaid.initialize).toBeCalledTimes(0)
    -  expect(mermaid.render).toBeCalledTimes(0)
    -  component.update()
    -  expect(mermaid.render).toBeCalledTimes(1)
    -  expect(mermaid.initialize).toBeCalledTimes(1)
    -  component.update()
    -  expect(mermaid.render).toBeCalledTimes(1)
    -  expect(mermaid.initialize).toBeCalledTimes(1)
    +  expectMermaidMatch(render(<Mermaid chart={''} config={{}} />))
     })
     
     it('renders with diagram', () => {
    -  const component = renderer.create(<Mermaid chart={`graph TD;
    -      A-->B;
    -      A-->C;
    -      B-->D;
    -      C-->D;`} config={{}} />)
    -  expect(mermaid.initialize).toBeCalledTimes(0)
    -  expect(mermaid.render).toBeCalledTimes(0)
    -  component.update()
    -  expect(mermaid.render).toBeCalledTimes(1)
    -  expect(mermaid.initialize).toBeCalledTimes(1)
    -  component.update()
    -  expect(mermaid.render).toBeCalledTimes(1)
    -  expect(mermaid.initialize).toBeCalledTimes(1)
    +  expectMermaidMatch(render(<svg><Mermaid chart={diagram} config={{}} /></svg>))
     })
     
    -it('initializes only once', async () => {
    -  const component = renderer.create(<>
    -        <Mermaid chart={'foo'} config={{}} />
    -        <Mermaid chart={'bar'} />
    -      </>)
    -  expect(mermaid.initialize).toBeCalledTimes(0)
    -  expect(mermaid.render).toBeCalledTimes(0)
    -  await waitFor(1000)
    -  component.update()
    -  expect(mermaid.render).toBeCalledTimes(2)
    +it('renders with diagram change', () => {
    +  const config = {}
    +  jest.useFakeTimers()
    +  const view = expectMermaidMatch(render(<Mermaid chart={diagram} config={config} />))
    +  view.rerender(<Mermaid chart={`graph TD;
    +D-->C;
    +D-->B;
    +C-->A;
    +B-->A;`} config={config} />)
    +  jest.advanceTimersByTime(1000)
    +  expectMermaidMatch(view)
    +  jest.useRealTimers()
    +  expect(mermaid.contentLoaded).toBeCalledTimes(1)
       expect(mermaid.initialize).toBeCalledTimes(1)
    -  component.update()
    -  expect(mermaid.render).toBeCalledTimes(2)
    +})
    +
    +it('initializes only once', () => {
    +  expectMermaidMatch(render(<>
    +    <Mermaid chart={'foo'} config={{}} />
    +    <Mermaid chart={'bar'} />
    +  </>))
    +  expect(mermaid.contentLoaded).toBeCalledTimes(1)
       expect(mermaid.initialize).toBeCalledTimes(1)
     })
     
     it('renders with mermaid config', () => {
    -  const component = renderer.create(<Mermaid chart={`graph TD;
    -      A-->B;
    -      A-->C;
    -      B-->D;
    -      C-->D;`} config={{ mermaid: { theme: 'dark' } } } />)
    -  expect(mermaid.initialize).toBeCalledTimes(0)
    -  expect(mermaid.render).toBeCalledTimes(0)
    -  component.update()
    -  expect(mermaid.render).toHaveBeenCalled()
    -  expect(mermaid.initialize).toHaveBeenNthCalledWith(1, { startOnLoad: true, theme: 'dark' })
    -  component.update()
    -  expect(mermaid.render).toBeCalledTimes(1)
    -  expect(mermaid.initialize).toBeCalledTimes(1)
    +  expectMermaidMatch(render(<Mermaid chart={diagram} config={{ mermaid: { theme: 'dark' } }} />))
    +  expect(mermaid.contentLoaded).toBeCalledTimes(1)
    +  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
     })
     
    -it('re-renders mermaid theme on html data-theme attribute change', async () => {
    -  const component = renderer.create(
    -    <html data-theme='light'>
    -      <Mermaid chart={`graph TD;
    -            A-->B;
    -            A-->C;
    -            B-->D;
    -            C-->D;`} config={{}} />
    -    </html>)
    -  expect(mermaid.initialize).toBeCalledTimes(0)
    -  expect(mermaid.render).toBeCalledTimes(0)
    -  component.update()
    -  expect(mermaid.render).toBeCalledTimes(1)
    -  expect(mermaid.initialize).toBeCalledTimes(1)
    -  component.update()
    -  expect(mermaid.render).toBeCalledTimes(1)
    -  expect(mermaid.initialize).toBeCalledTimes(1)
    +it('renders with mermaid config change', () => {
    +  const view = expectMermaidMatch(render(<Mermaid chart={diagram} config={{ mermaid: { theme: 'dark' } }} />))
    +  view.baseElement.querySelectorAll('div.mermaid').forEach((v) => {
    +    v.setAttribute('data-processed', 'true')
    +  })
    +  expect(mermaid.contentLoaded).toBeCalledTimes(1)
    +  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
    +  view.rerender(<Mermaid chart={diagram} config={{ mermaid: { theme: 'forest' } }} />)
    +  // await waitFor(1000)
    +  expectMermaidMatch(view)
    +  expect(mermaid.contentLoaded).toBeCalledTimes(2)
    +  expect(mermaid.initialize).toHaveBeenNthCalledWith(2, { startOnLoad: true, theme: 'forest' })
    +})
     
    -  component.update(
    -    <html data-theme='dark'>
    -      <Mermaid chart={`graph TD;
    -            A-->B;
    -            A-->C;
    -            B-->D;
    -            C-->D;`} config={{}} />
    -        </html>)
    +it('renders with string mermaid config', () => {
    +  expectMermaidMatch(render(<Mermaid chart={diagram} config={JSON.stringify({ mermaid: { theme: 'dark' } })} />))
    +  expect(mermaid.contentLoaded).toBeCalledTimes(1)
    +  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
    +})
     
    -  // Time for mutation observer to notice change.
    -  await waitFor(2000)
    +it('re-renders mermaid theme on html data-theme attribute change', () => {
    +  const component = render(
    +    <Mermaid chart={diagram} config={{}} />)
     
    -  expect(mermaid.render).toBeCalledTimes(2)
    -  expect(mermaid.initialize).toBeCalledTimes(2)
    -})
    +  expectMermaidMatch(component)
    +  expect(mermaid.contentLoaded).toBeCalledTimes(1)
    +  expect(mermaid.initialize).toBeCalledTimes(1)
    +  expect(getThemeSpy).toBeCalledTimes(1)
     
    -it('renders the output of mermaid into the div', async () => {
    -  const expectedOutput = 'mermaid output'
    -  mermaid.render = jest.fn((_, __, cb) => {
    -    if (cb) cb(expectedOutput, () => 0)
    -    return expectedOutput
    -  })
    +  act(() => document.querySelector('html')!.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY))
     
    -  let component: any
    -  renderer.act(() => {
    -    component = renderer.create(
    -      <Mermaid chart={`graph TD;
    -            A-->B;
    -            A-->C;
    -            B-->D;
    -            C-->D;`} config={{}} />
    -    )
    -  })
    +  expectMermaidMatch(component)
     
    +  expect(mermaid.contentLoaded).toBeCalledTimes(1)
       expect(mermaid.initialize).toBeCalledTimes(1)
    -  expect(mermaid.render).toBeCalledTimes(1)
    -  expect(component.toJSON()).toMatchSnapshot()
    -})
    +  expect(getThemeSpy).toBeCalledTimes(1)
     
    -describe('changing the theme at runtime', () => {
    -  let useRefSpy: jest.SpyInstance
    -  let html: HTMLHtmlElement
    +  act(() => document.querySelector('html')!.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY))
     
    -  beforeEach(() => {
    -    html = document.createElement('html')
    -    html.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY)
    -    useRefSpy = jest.spyOn(document, 'querySelector').mockReturnValue(html)
    -  })
    +  expectMermaidMatch(component)
    +})
     
    -  afterEach(() => {
    -    expect(useRefSpy).toHaveBeenCalled()
    -  })
    +it('does not react to non-theme attribute changes of html', () => {
    +  const component = render(<Mermaid chart={diagram} config={{}} />)
     
    -  it('reacts to changed theme', async () => {
    -    const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
    -    renderer.act(() => {
    -      renderer.create(
    -        <Mermaid chart={`graph TD;
    -              A-->B;
    -              A-->C;
    -              B-->D;
    -              C-->D;`} config={{}} />
    -      )
    -    })
    -
    -    await renderer.act(async () => {
    -      html.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY)
    -      await waitFor(1000)
    -    })
    -
    -    expect(getThemeSpy.mock.calls.length).toBeGreaterThan(2)
    -  })
    +  expectMermaidMatch(component)
    +  expect(mermaid.contentLoaded).toBeCalledTimes(1)
    +  expect(mermaid.initialize).toBeCalledTimes(1)
     
    -  it('does not react to non-theme attribute changes of html', async () => {
    -    const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
    -    renderer.act(() => {
    -      renderer.create(
    -        <Mermaid chart={`graph TD;
    -              A-->B;
    -              A-->C;
    -              B-->D;
    -              C-->D;`} config={{}} />
    -      )
    -    })
    -
    -    await renderer.act(async () => {
    -      html.setAttribute('manifest', 'some-value')
    -      await waitFor(1000)
    -    })
    -    expect(getThemeSpy).toHaveBeenCalledTimes(2)
    -  })
    +  act(() => document.querySelector('html')!.setAttribute('manifest', 'some-value'))
    +
    +  expectMermaidMatch(component)
     })
    
  • src/Mermaid.tsx+22 28 modified
    @@ -5,19 +5,12 @@
      * license file in the root directory of this source tree.
      */
     
    -import React, { useEffect, useState, ReactElement } from 'react'
    +import React, { useEffect, useState, ReactElement, useMemo } from 'react'
     import mermaid from 'mermaid'
    -import mermaidAPI from 'mermaid/mermaidAPI'
     
     import { Config } from './config.model'
     import { getTheme } from './theme.helper'
     
    -/**
    - * Assign a unique ID to each mermaid svg as per requirements
    - * of `mermaid.render`.
    - */
    -let id = 0
    -
     /**
      * Properties for Mermaid component.
      */
    @@ -30,7 +23,7 @@ export type MermaidProps = {
       /**
        * Config to initialize mermaid with.
        */
    -  config?: Config
    +  config?: Config | string
     }
     
     /**
    @@ -40,26 +33,29 @@ export type MermaidProps = {
      * @param param1 Config.
      * @returns The component.
      */
    -export const Mermaid = ({ chart, config }: MermaidProps): ReactElement<MermaidProps> => {
    +export const Mermaid = ({ chart, config: configSrc }: MermaidProps): ReactElement<MermaidProps> => {
       // Due to Docusaurus not correctly parsing client-side from server-side modules, use the provided workaround
       // found in the accompanying issue: https://github.com/facebook/docusaurus/issues/4268#issuecomment-783553084
       /* istanbul ignore next */
       if (typeof window === 'undefined') {
    -    return <div></div>
    +    return <div className="mermaid" data-mermaid-src={chart}>{chart}</div>
       }
     
    +  const config: Config = useMemo(() => typeof configSrc === 'string' ? JSON.parse(configSrc) : configSrc, [configSrc])
    +
       const html: HTMLHtmlElement = document.querySelector('html')!
     
    -  // Watch for changes in theme in the HTML attribute `data-theme`.
    -  const [theme, setTheme] = useState<mermaidAPI.Theme>(getTheme(html, config))
    +  const [rerender, setRerender] = useState<boolean>(false)
    +
    +  const theme = useMemo(() => getTheme(html, config), [config, rerender])
     
       useEffect(() => {
         const observer = new MutationObserver((mutations) => {
           for (const mutation of mutations) {
             if (mutation.type !== 'attributes' || mutation.attributeName !== 'data-theme') {
               continue
             }
    -        setTheme(getTheme(mutation.target as HTMLHtmlElement, config))
    +        setRerender((cur) => !cur)
             break
           }
         })
    @@ -72,28 +68,26 @@ export const Mermaid = ({ chart, config }: MermaidProps): ReactElement<MermaidPr
             // Do nothing
           }
         }
    -  }, [chart, config, theme])
    +  }, [])
     
    -  // When theme updates, rerender the SVG.
    -  const [svg, setSvg] = useState<string>('')
       useEffect(() => {
    -    const render = () => {
    -      mermaid.render(`mermaid-svg-${id.toString()}`, chart, (renderedSvg) => setSvg(renderedSvg))
    -      id++
    -    }
    -
         if (config) {
           if (config.mermaid) {
             mermaid.initialize({ startOnLoad: true, ...config.mermaid, theme })
           } else {
             mermaid.initialize({ startOnLoad: true, theme })
           }
    -      render()
    -    } else {
    -      // Is there a better way?
    -      setTimeout(render, 0)
    +      document.querySelectorAll('div.mermaid[data-processed="true"]').forEach((v) => {
    +        v.removeAttribute('data-processed')
    +        v.innerHTML = v.getAttribute('data-mermaid-src') as string
    +      })
    +      mermaid.contentLoaded()
         }
    -  }, [theme, chart])
    +  }, [config, theme])
    +
    +  useEffect(() => {
    +    setTimeout(() => mermaid.contentLoaded, 0)
    +  }, [chart])
     
    -  return <div dangerouslySetInnerHTML={{ __html: svg }}></div>
    +  return <div className="mermaid" data-mermaid-src={chart}>{chart}</div>
     }
    
  • src/__snapshots__/Mermaid.spec.tsx.snap+333 8 modified
    @@ -1,11 +1,336 @@
     // Jest Snapshot v1, https://goo.gl/fbAQLP
     
    -exports[`renders the output of mermaid into the div 1`] = `
    -<div
    -  dangerouslySetInnerHTML={
    -    Object {
    -      "__html": "mermaid output",
    -    }
    -  }
    -/>
    +exports[`does not react to non-theme attribute changes of html 1`] = `
    +<html
    +  data-theme="light"
    +>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +      >
    +        graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`does not react to non-theme attribute changes of html 2`] = `
    +<html
    +  data-theme="light"
    +  manifest="some-value"
    +>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +      >
    +        graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`initializes only once 1`] = `
    +<html>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="foo"
    +      >
    +        foo
    +      </div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="bar"
    +      >
    +        bar
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`re-renders mermaid theme on html data-theme attribute change 1`] = `
    +<html>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +      >
    +        graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`re-renders mermaid theme on html data-theme attribute change 2`] = `
    +<html
    +  data-theme="dark"
    +>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +      >
    +        graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`re-renders mermaid theme on html data-theme attribute change 3`] = `
    +<html
    +  data-theme="light"
    +>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +      >
    +        graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`renders with diagram 1`] = `
    +<html>
    +  <head />
    +  <body>
    +    <div>
    +      <svg>
    +        <div
    +          class="mermaid"
    +          data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +        >
    +          graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +        </div>
    +      </svg>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`renders with diagram change 1`] = `
    +<html>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +      >
    +        graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`renders with diagram change 2`] = `
    +<html>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +D-->C;
    +D-->B;
    +C-->A;
    +B-->A;"
    +      >
    +        graph TD;
    +D--&gt;C;
    +D--&gt;B;
    +C--&gt;A;
    +B--&gt;A;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`renders with mermaid config 1`] = `
    +<html>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +      >
    +        graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`renders with mermaid config change 1`] = `
    +<html>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +      >
    +        graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`renders with mermaid config change 2`] = `
    +<html>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +      >
    +        graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`renders with string mermaid config 1`] = `
    +<html>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src="graph TD;
    +A-->B;
    +A-->C;
    +B-->D;
    +C-->D;"
    +      >
    +        graph TD;
    +A--&gt;B;
    +A--&gt;C;
    +B--&gt;D;
    +C--&gt;D;
    +      </div>
    +    </div>
    +  </body>
    +</html>
    +`;
    +
    +exports[`renders without diagram 1`] = `
    +<html>
    +  <head />
    +  <body>
    +    <div>
    +      <div
    +        class="mermaid"
    +        data-mermaid-src=""
    +      />
    +    </div>
    +  </body>
    +</html>
     `;
    
  • yarn.lock+97 20 modified
    @@ -1501,7 +1501,15 @@
         pirates "^4.0.0"
         source-map-support "^0.5.16"
     
    -"@babel/runtime@^7.8.4":
    +"@babel/runtime-corejs3@^7.10.2":
    +  version "7.18.9"
    +  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz#7bacecd1cb2dd694eacd32a91fcf7021c20770ae"
    +  integrity sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==
    +  dependencies:
    +    core-js-pure "^3.20.2"
    +    regenerator-runtime "^0.13.4"
    +
    +"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4":
       version "7.18.9"
       resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
       integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
    @@ -1852,6 +1860,17 @@
         source-map "^0.6.1"
         write-file-atomic "^3.0.0"
     
    +"@jest/types@^26.6.2":
    +  version "26.6.2"
    +  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
    +  integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
    +  dependencies:
    +    "@types/istanbul-lib-coverage" "^2.0.0"
    +    "@types/istanbul-reports" "^3.0.0"
    +    "@types/node" "*"
    +    "@types/yargs" "^15.0.0"
    +    chalk "^4.0.0"
    +
     "@jest/types@^27.0.6":
       version "27.0.6"
       resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.0.6.tgz#9a992bc517e0c49f035938b8549719c2de40706b"
    @@ -2114,11 +2133,38 @@
       dependencies:
         "@sinonjs/commons" "^1.7.0"
     
    +"@testing-library/dom@^7.28.1":
    +  version "7.31.2"
    +  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a"
    +  integrity sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==
    +  dependencies:
    +    "@babel/code-frame" "^7.10.4"
    +    "@babel/runtime" "^7.12.5"
    +    "@types/aria-query" "^4.2.0"
    +    aria-query "^4.2.2"
    +    chalk "^4.1.0"
    +    dom-accessibility-api "^0.5.6"
    +    lz-string "^1.4.4"
    +    pretty-format "^26.6.2"
    +
    +"@testing-library/react@^11.1.0":
    +  version "11.2.7"
    +  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818"
    +  integrity sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==
    +  dependencies:
    +    "@babel/runtime" "^7.12.5"
    +    "@testing-library/dom" "^7.28.1"
    +
     "@tootallnate/once@1":
       version "1.1.2"
       resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
       integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
     
    +"@types/aria-query@^4.2.0":
    +  version "4.2.2"
    +  resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
    +  integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
    +
     "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
       version "7.1.15"
       resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.15.tgz#2ccfb1ad55a02c83f8e0ad327cbc332f55eb1024"
    @@ -2279,6 +2325,13 @@
       resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
       integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==
     
    +"@types/yargs@^15.0.0":
    +  version "15.0.14"
    +  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06"
    +  integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==
    +  dependencies:
    +    "@types/yargs-parser" "*"
    +
     "@types/yargs@^16.0.0":
       version "16.0.4"
       resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977"
    @@ -2492,6 +2545,14 @@ argparse@^2.0.1:
       resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
       integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
     
    +aria-query@^4.2.2:
    +  version "4.2.2"
    +  resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
    +  integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
    +  dependencies:
    +    "@babel/runtime" "^7.10.2"
    +    "@babel/runtime-corejs3" "^7.10.2"
    +
     arr-diff@^4.0.0:
       version "4.0.0"
       resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz"
    @@ -3114,6 +3175,11 @@ core-js-compat@^3.21.0, core-js-compat@^3.22.1:
         browserslist "^4.21.3"
         semver "7.0.0"
     
    +core-js-pure@^3.20.2:
    +  version "3.24.1"
    +  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.24.1.tgz#8839dde5da545521bf282feb7dc6d0b425f39fd3"
    +  integrity sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg==
    +
     cosmiconfig@^7.0.0:
       version "7.0.0"
       resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz"
    @@ -3563,6 +3629,11 @@ doctrine@^3.0.0:
       dependencies:
         esutils "^2.0.2"
     
    +dom-accessibility-api@^0.5.6:
    +  version "0.5.14"
    +  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56"
    +  integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==
    +
     domexception@^2.0.1:
       version "2.0.1"
       resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
    @@ -5722,6 +5793,11 @@ lru-cache@^6.0.0:
       dependencies:
         yallist "^4.0.0"
     
    +lz-string@^1.4.4:
    +  version "1.4.4"
    +  resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
    +  integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==
    +
     magic-string@^0.25.7:
       version "0.25.9"
       resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
    @@ -6320,6 +6396,16 @@ prelude-ls@~1.1.2:
       resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
       integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
     
    +pretty-format@^26.6.2:
    +  version "26.6.2"
    +  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
    +  integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
    +  dependencies:
    +    "@jest/types" "^26.6.2"
    +    ansi-regex "^5.0.0"
    +    ansi-styles "^4.0.0"
    +    react-is "^17.0.1"
    +
     pretty-format@^27.0.0, pretty-format@^27.4.6:
       version "27.4.6"
       resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.6.tgz#1b784d2f53c68db31797b2348fa39b49e31846b7"
    @@ -6373,33 +6459,24 @@ queue-microtask@^1.2.2:
       resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
       integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
     
    -"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2:
    +react-dom@^17.0.0:
       version "17.0.2"
    -  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
    -  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
    +  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
    +  integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
    +  dependencies:
    +    loose-envify "^1.1.0"
    +    object-assign "^4.1.1"
    +    scheduler "^0.20.2"
     
     react-is@^16.8.1:
       version "16.13.1"
       resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
       integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
     
    -react-shallow-renderer@^16.13.1:
    -  version "16.14.1"
    -  resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124"
    -  integrity sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==
    -  dependencies:
    -    object-assign "^4.1.1"
    -    react-is "^16.12.0 || ^17.0.0"
    -
    -react-test-renderer@^17.0.2:
    +react-is@^17.0.1:
       version "17.0.2"
    -  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"
    -  integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==
    -  dependencies:
    -    object-assign "^4.1.1"
    -    react-is "^17.0.2"
    -    react-shallow-renderer "^16.13.1"
    -    scheduler "^0.20.2"
    +  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
    +  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
     
     react@^17.0.1:
       version "17.0.2"
    

Vulnerability mechanics

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

References

4

News mentions

0

No linked articles in our index yet.