Skip to content

Commit 810db06

Browse files
authored
Merge commit from fork
[v10] fix: prevent unbalanced CSS in `classDef`s
2 parents 7f3e4c1 + 8fead23 commit 810db06

7 files changed

Lines changed: 125 additions & 19 deletions

File tree

cypress/integration/other/ghsa.spec.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,15 @@ describe('CSS injections', () => {
2020
flowchart: { htmlLabels: true },
2121
});
2222
});
23+
it('should sanitize CSS in class definitions', () => {
24+
urlSnapshotTest('http://localhost:9000/css-injection.html', {
25+
logLevel: 1,
26+
flowchart: { htmlLabels: false },
27+
});
28+
cy.get('.otp-3').should(
29+
'not.have.css',
30+
'background-image',
31+
'url("https://example.test/3.png")'
32+
);
33+
});
2334
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Mermaid Quick Test Page</title>
6+
7+
<style>
8+
.otp {
9+
width: 30px;
10+
height: 30px;
11+
position: relative;
12+
}
13+
.otp-1:before {
14+
content: '1';
15+
}
16+
.otp-2:before {
17+
content: '2';
18+
}
19+
.otp-3:before {
20+
content: '3';
21+
}
22+
.otp-4:before {
23+
content: '4';
24+
}
25+
</style>
26+
</head>
27+
28+
<body>
29+
<section>
30+
<h2>Test CSS Injection</h2>
31+
32+
<h3>Here is some secret info:</h3>
33+
34+
<p>Your OTP is:</p>
35+
36+
<!-- Contrived example, but could be used on some sites -->
37+
<div style="flex-direction: row; display: flex">
38+
<div class="otp otp-3"></div>
39+
<div class="otp otp-2"></div>
40+
<div class="otp otp-4"></div>
41+
</div>
42+
43+
<p>If the above numbers are red, then we have a working CSS injection.</p>
44+
</section>
45+
46+
<pre class="mermaid">
47+
stateDiagram-v2
48+
classDef x }}body{.otp{border: 1px solid red}.otp{color: red}.otp-1{ background-image: url("https://example.test/1.png")}.otp-2{ background-image: url("https://example.test/2.png")}.otp-3{ background-image: url("https://example.test/3.png")}.otp-4{ background-image: url("https://example.test/4.png")}.otp-5{ background-image: url("https://example.test/5.png")}}
49+
[*] --> A
50+
</pre>
51+
52+
<script type="module">
53+
import mermaid from './mermaid.esm.mjs';
54+
mermaid.initialize({ startOnLoad: false, logLevel: 0 });
55+
await mermaid.run();
56+
if (window.Cypress) {
57+
window.rendered = true;
58+
}
59+
</script>
60+
</body>
61+
</html>

docs/config/setup/interfaces/mermaidAPI.ParseOptions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
1616

1717
#### Defined in
1818

19-
[mermaidAPI.ts:60](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L60)
19+
[mermaidAPI.ts:61](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L61)

docs/config/setup/interfaces/mermaidAPI.RenderResult.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
3939
4040
#### Defined in
4141
42-
[mermaidAPI.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L80)
42+
[mermaidAPI.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L81)
4343
4444
---
4545
@@ -51,4 +51,4 @@ The svg code for the rendered graph.
5151
5252
#### Defined in
5353
54-
[mermaidAPI.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L70)
54+
[mermaidAPI.ts:71](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L71)

docs/config/setup/modules/mermaidAPI.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
2525

2626
#### Defined in
2727

28-
[mermaidAPI.ts:64](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L64)
28+
[mermaidAPI.ts:65](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L65)
2929

3030
## Variables
3131

@@ -96,7 +96,7 @@ mermaid.initialize(config);
9696

9797
#### Defined in
9898

99-
[mermaidAPI.ts:608](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L608)
99+
[mermaidAPI.ts:610](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L610)
100100

101101
## Functions
102102

@@ -127,7 +127,7 @@ Return the last node appended
127127

128128
#### Defined in
129129

130-
[mermaidAPI.ts:263](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L263)
130+
[mermaidAPI.ts:265](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L265)
131131

132132
---
133133

@@ -153,7 +153,7 @@ the cleaned up svgCode
153153

154154
#### Defined in
155155

156-
[mermaidAPI.ts:209](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L209)
156+
[mermaidAPI.ts:211](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L211)
157157

158158
---
159159

@@ -178,7 +178,7 @@ the string with all the user styles
178178

179179
#### Defined in
180180

181-
[mermaidAPI.ts:139](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L139)
181+
[mermaidAPI.ts:141](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L141)
182182

183183
---
184184

@@ -201,7 +201,7 @@ the string with all the user styles
201201

202202
#### Defined in
203203

204-
[mermaidAPI.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L186)
204+
[mermaidAPI.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L188)
205205

206206
---
207207

@@ -228,7 +228,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
228228

229229
#### Defined in
230230

231-
[mermaidAPI.ts:124](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L124)
231+
[mermaidAPI.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L125)
232232

233233
---
234234

@@ -254,7 +254,7 @@ Put the svgCode into an iFrame. Return the iFrame code
254254

255255
#### Defined in
256256

257-
[mermaidAPI.ts:240](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L240)
257+
[mermaidAPI.ts:242](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L242)
258258

259259
---
260260

@@ -279,4 +279,4 @@ Remove any existing elements from the given document
279279

280280
#### Defined in
281281

282-
[mermaidAPI.ts:313](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L313)
282+
[mermaidAPI.ts:315](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L315)

packages/mermaid/src/mermaidAPI.spec.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,26 @@ import assignWithDepth from './assignWithDepth.js';
5353
vi.mock('./styles.js', () => {
5454
return {
5555
addStylesForDiagram: vi.fn(),
56-
default: vi.fn().mockReturnValue(' .userStyle { font-weight:bold; }'),
56+
default: vi.fn().mockImplementation(
57+
(_type, userStyles, _options) => `
58+
& .edge-pattern-dashed{
59+
stroke-dasharray: 3;
60+
}
61+
62+
${userStyles}
63+
`
64+
),
5765
};
5866
});
5967
import getStyles from './styles.js';
6068

61-
vi.mock('stylis', () => {
69+
vi.mock('stylis', async (importOriginal) => {
70+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
71+
const original: typeof import('stylis') = await importOriginal();
6272
return {
63-
stringify: vi.fn(),
64-
compile: vi.fn(),
65-
serialize: vi.fn().mockReturnValue('stylis serialized css'),
73+
stringify: vi.fn().mockImplementation(original.stringify),
74+
compile: vi.fn().mockImplementation(original.compile),
75+
serialize: vi.fn().mockImplementation(original.serialize),
6676
};
6777
});
6878
import { compile, serialize } from 'stylis';
@@ -455,7 +465,29 @@ describe('mermaidAPI', () => {
455465
const result = createUserStyles(mockConfig, 'someDiagram', {}, 'someId');
456466
expect(compile).toHaveBeenCalled();
457467
expect(serialize).toHaveBeenCalled();
458-
expect(result).toEqual('stylis serialized css');
468+
expect(result).toEqual('someId .edge-pattern-dashed{stroke-dasharray:3;}');
469+
});
470+
471+
it('should sanitize CSS to avoid unbalanced braces', () => {
472+
const result = createUserStyles(
473+
mockConfig,
474+
'someDiagram',
475+
Object.fromEntries(
476+
Object.entries({
477+
classDef1: {
478+
styles: ['}*{ background-image: url("https://example.test")}'],
479+
textStyles: [],
480+
},
481+
classDef2: {
482+
styles: ['color: purple;'],
483+
},
484+
}).map(([id, value]) => [id, { ...value, id }])
485+
),
486+
'someId'
487+
);
488+
expect(result).toEqual(
489+
'someId .edge-pattern-dashed{stroke-dasharray:3;}someId .classDef2>*{color:purple;}someId .classDef2 span{color:purple;}'
490+
);
459491
});
460492
});
461493

packages/mermaid/src/mermaidAPI.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.
3131
import type { DiagramMetadata, DiagramStyleClassDef } from './diagram-api/types.js';
3232
import { preprocessDiagram } from './preprocess.js';
3333
import { decodeEntities } from './utils.js';
34+
import { sanitizeCss } from './utils/sanitizeDirective.js';
3435

3536
const MAX_TEXTLENGTH = 50_000;
3637
const MAX_TEXTLENGTH_EXCEEDED_MSG =
@@ -126,7 +127,8 @@ export const cssImportantStyles = (
126127
element: string,
127128
cssClasses: string[] = []
128129
): string => {
129-
return `\n.${cssClass} ${element} { ${cssClasses.join(' !important; ')} !important; }`;
130+
const declarationBlock = sanitizeCss(`{ ${cssClasses.join(' !important; ')} !important; }`);
131+
return `\n.${cssClass} ${element} ${declarationBlock}`;
130132
};
131133

132134
/**

0 commit comments

Comments
 (0)