From 1096d10cb41702878fb11ec663c8c74da89a5047 Mon Sep 17 00:00:00 2001 From: Morreski Date: Fri, 11 Jun 2021 11:07:27 +0200 Subject: [PATCH 1/4] Force securityLevel option to 'strict' by default to mitigate XSS attacks --- src/lib/components/view.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/components/view.svelte b/src/lib/components/view.svelte index 718a158f..bca08fd5 100644 --- a/src/lib/components/view.svelte +++ b/src/lib/components/view.svelte @@ -25,7 +25,9 @@ const scroll = container.parentElement.parentElement.parentElement.scrollTop; container.innerHTML = code; delete container.dataset.processed; - mermaid.initialize(Object.assign({}, JSON.parse(state.mermaid))); + mermaid.initialize( + Object.assign({}, JSON.parse(state.mermaid), { securityLevel: 'strict' }) + ); // Monkey patch: force securityLevel: strict to mitigate XSS attacks mermaid.render('graph-div', code, (svgCode) => { container.innerHTML = svgCode; }); From cec1dc4df9c642765f06fa422e2ca7d85260049f Mon Sep 17 00:00:00 2001 From: Morreski Date: Fri, 11 Jun 2021 11:07:44 +0200 Subject: [PATCH 2/4] Handle tag support in graphs --- src/lib/components/actions.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/components/actions.svelte b/src/lib/components/actions.svelte index a119c4d5..802f2c46 100644 --- a/src/lib/components/actions.svelte +++ b/src/lib/components/actions.svelte @@ -13,7 +13,9 @@ if (!svg) { svg = document.querySelector('#container svg'); } - const svgString = svg.outerHTML.replaceAll('
', '
'); + const svgString = svg.outerHTML + .replaceAll('
', '
') + .replaceAll(/]*)>/g, (m, g) => ``); return toBase64(svgString); }; From fbe2f127b84ffacb3ae735f81e013334f56feb70 Mon Sep 17 00:00:00 2001 From: Morreski Date: Mon, 14 Jun 2021 22:38:44 +0200 Subject: [PATCH 3/4] Only strip securityLevel option when state is loaded from URL --- cypress/integration/loadSite.spec.ts | 10 ++++++++++ src/lib/components/view.svelte | 6 ++---- src/lib/util/state.ts | 8 +++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cypress/integration/loadSite.spec.ts b/cypress/integration/loadSite.spec.ts index 0c58d373..c83d6d14 100644 --- a/cypress/integration/loadSite.spec.ts +++ b/cypress/integration/loadSite.spec.ts @@ -29,4 +29,14 @@ describe('Site Loads', () => { cy.contains('Class Diagram').click(); cy.contains('classDiagram'); }); + + it('should prevent setting the "securityLevel" option via URL', () => { + const b64State = btoa( + `{"code":"graph TD\\nA[\\"\\"]","mermaid":"{\\"securityLevel\\": \\"loose\\", \\"theme\\": \\"forest\\"}","updateEditor":true,"autoSync":true,"updateDiagram":true}` + ); + cy.visit(`/edit#${b64State}`); + cy.contains('Config').click(); + cy.contains('forest').should('exist'); + cy.contains('securityLevel').should('not.exist'); + }); }); diff --git a/src/lib/components/view.svelte b/src/lib/components/view.svelte index bca08fd5..4263be68 100644 --- a/src/lib/components/view.svelte +++ b/src/lib/components/view.svelte @@ -5,7 +5,7 @@ import { onMount } from 'svelte'; import type { Mermaid } from 'mermaid'; - const mermaid: Mermaid = (window.mermaid as unknown) as Mermaid; + const mermaid: Mermaid = window.mermaid as unknown as Mermaid; let code = ''; let container: HTMLDivElement; let error = false; @@ -25,9 +25,7 @@ const scroll = container.parentElement.parentElement.parentElement.scrollTop; container.innerHTML = code; delete container.dataset.processed; - mermaid.initialize( - Object.assign({}, JSON.parse(state.mermaid), { securityLevel: 'strict' }) - ); // Monkey patch: force securityLevel: strict to mitigate XSS attacks + mermaid.initialize(Object.assign({}, JSON.parse(state.mermaid))); mermaid.render('graph-div', code, (svgCode) => { container.innerHTML = svgCode; }); diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts index ebb839d5..9a599ccc 100644 --- a/src/lib/util/state.ts +++ b/src/lib/util/state.ts @@ -35,9 +35,11 @@ export const loadState = (data: string): void => { const stateStr = fromBase64(data); console.log(`Tring to load state: ${stateStr}`); state = JSON.parse(stateStr); - if (typeof state.mermaid !== 'string') { - state.mermaid = JSON.stringify(state.mermaid, null, 2); - } + const mermaidConfig = + typeof state.mermaid === 'string' ? JSON.parse(state.mermaid) : state.mermaid; + delete mermaidConfig.securityLevel; // Prevent setting overriding securityLevel when loading state to mitigate possible XSS attack + + state.mermaid = JSON.stringify(mermaidConfig, null, 2); } catch (e) { if (data) { console.error('Init error', e); From a907674539a0b99645eefe9fd09b4d7a9370a93c Mon Sep 17 00:00:00 2001 From: Enguerrand Pelletier Date: Tue, 15 Jun 2021 09:15:47 +0200 Subject: [PATCH 4/4] Trigger an alert when securityLevel is stripped Co-authored-by: Sidharth Vinod --- src/lib/util/state.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts index 9a599ccc..d0f8ee82 100644 --- a/src/lib/util/state.ts +++ b/src/lib/util/state.ts @@ -37,7 +37,10 @@ export const loadState = (data: string): void => { state = JSON.parse(stateStr); const mermaidConfig = typeof state.mermaid === 'string' ? JSON.parse(state.mermaid) : state.mermaid; - delete mermaidConfig.securityLevel; // Prevent setting overriding securityLevel when loading state to mitigate possible XSS attack + if(mermaidConfig.securityLevel) { + alert(`securityLevel was removed from config. Please add "securityLevel":"${mermaidConfig.securityLevel}" to your config if you trust the source of this Diagram`); + delete mermaidConfig.securityLevel; // Prevent setting overriding securityLevel when loading state to mitigate possible XSS attack + } state.mermaid = JSON.stringify(mermaidConfig, null, 2); } catch (e) {