mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-18 23:09:49 +02:00
Merge commit from fork
fix: Sanitize icons and icon labels
This commit is contained in:
7
.changeset/good-weeks-tickle.md
Normal file
7
.changeset/good-weeks-tickle.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: sanitize icon labels and icon SVGs
|
||||||
|
|
||||||
|
Resolves CVE-2025-54880 reported by @fourcube
|
@@ -142,6 +142,17 @@ describe('XSS', () => {
|
|||||||
cy.get('#the-malware').should('not.exist');
|
cy.get('#the-malware').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should sanitize icon labels in architecture diagrams', () => {
|
||||||
|
const str = JSON.stringify({
|
||||||
|
code: `architecture-beta
|
||||||
|
group api(cloud)[API]
|
||||||
|
service db "<img src=x onerror=\\"xssAttack()\\">" [Database] in api`,
|
||||||
|
});
|
||||||
|
imgSnapshotTest(utf8ToB64(str), {}, true);
|
||||||
|
cy.wait(1000);
|
||||||
|
cy.get('#the-malware').should('not.exist');
|
||||||
|
});
|
||||||
|
|
||||||
it('should sanitize katex blocks', () => {
|
it('should sanitize katex blocks', () => {
|
||||||
const str = JSON.stringify({
|
const str = JSON.stringify({
|
||||||
code: `sequenceDiagram
|
code: `sequenceDiagram
|
||||||
|
@@ -3,6 +3,7 @@ import { getConfig } from '../../diagram-api/diagramAPI.js';
|
|||||||
import { createText } from '../../rendering-util/createText.js';
|
import { createText } from '../../rendering-util/createText.js';
|
||||||
import { getIconSVG } from '../../rendering-util/icons.js';
|
import { getIconSVG } from '../../rendering-util/icons.js';
|
||||||
import type { D3Element } from '../../types.js';
|
import type { D3Element } from '../../types.js';
|
||||||
|
import { sanitizeText } from '../common/common.js';
|
||||||
import type { ArchitectureDB } from './architectureDb.js';
|
import type { ArchitectureDB } from './architectureDb.js';
|
||||||
import { architectureIcons } from './architectureIcons.js';
|
import { architectureIcons } from './architectureIcons.js';
|
||||||
import {
|
import {
|
||||||
@@ -271,6 +272,7 @@ export const drawServices = async function (
|
|||||||
elem: D3Element,
|
elem: D3Element,
|
||||||
services: ArchitectureService[]
|
services: ArchitectureService[]
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
|
const config = getConfig();
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
const serviceElem = elem.append('g');
|
const serviceElem = elem.append('g');
|
||||||
const iconSize = db.getConfigField('iconSize');
|
const iconSize = db.getConfigField('iconSize');
|
||||||
@@ -285,7 +287,7 @@ export const drawServices = async function (
|
|||||||
width: iconSize * 1.5,
|
width: iconSize * 1.5,
|
||||||
classes: 'architecture-service-label',
|
classes: 'architecture-service-label',
|
||||||
},
|
},
|
||||||
getConfig()
|
config
|
||||||
);
|
);
|
||||||
|
|
||||||
textElem
|
textElem
|
||||||
@@ -320,7 +322,7 @@ export const drawServices = async function (
|
|||||||
.attr('class', 'node-icon-text')
|
.attr('class', 'node-icon-text')
|
||||||
.attr('style', `height: ${iconSize}px;`)
|
.attr('style', `height: ${iconSize}px;`)
|
||||||
.append('div')
|
.append('div')
|
||||||
.html(service.iconText);
|
.html(sanitizeText(service.iconText, config));
|
||||||
const fontSize =
|
const fontSize =
|
||||||
parseInt(
|
parseInt(
|
||||||
window
|
window
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { replaceIconSubstring } from './createText.js';
|
import { sanitizeText } from '../diagram-api/diagramAPI.js';
|
||||||
import mermaid from '../mermaid.js';
|
import mermaid from '../mermaid.js';
|
||||||
|
import { replaceIconSubstring } from './createText.js';
|
||||||
|
|
||||||
describe('replaceIconSubstring', () => {
|
describe('replaceIconSubstring', () => {
|
||||||
it('converts FontAwesome icon notations to HTML tags', async () => {
|
it('converts FontAwesome icon notations to HTML tags', async () => {
|
||||||
@@ -56,7 +57,7 @@ describe('replaceIconSubstring', () => {
|
|||||||
]);
|
]);
|
||||||
const input = 'Icons galore: fa:fa-bell';
|
const input = 'Icons galore: fa:fa-bell';
|
||||||
const output = await replaceIconSubstring(input);
|
const output = await replaceIconSubstring(input);
|
||||||
const expected = staticBellIconPack.icons.bell.body;
|
const expected = sanitizeText(staticBellIconPack.icons.bell.body);
|
||||||
expect(output).toContain(expected);
|
expect(output).toContain(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import { log } from '../logger.js';
|
|
||||||
import type { ExtendedIconifyIcon, IconifyIcon, IconifyJSON } from '@iconify/types';
|
import type { ExtendedIconifyIcon, IconifyIcon, IconifyJSON } from '@iconify/types';
|
||||||
import type { IconifyIconCustomisations } from '@iconify/utils';
|
import type { IconifyIconCustomisations } from '@iconify/utils';
|
||||||
import { getIconData, iconToHTML, iconToSVG, replaceIDs, stringToIcon } from '@iconify/utils';
|
import { getIconData, iconToHTML, iconToSVG, replaceIDs, stringToIcon } from '@iconify/utils';
|
||||||
|
import { getConfig } from '../config.js';
|
||||||
|
import { sanitizeText } from '../diagrams/common/common.js';
|
||||||
|
import { log } from '../logger.js';
|
||||||
|
|
||||||
interface AsyncIconLoader {
|
interface AsyncIconLoader {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -100,5 +102,5 @@ export const getIconSVG = async (
|
|||||||
...renderData.attributes,
|
...renderData.attributes,
|
||||||
...extraAttributes,
|
...extraAttributes,
|
||||||
});
|
});
|
||||||
return svg;
|
return sanitizeText(svg, getConfig());
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user