Skip to content

Commit 4a36a17

Browse files
bydbclaude
andcommitted
Bump version to 1.0.22-beta with security hardening
Add DOMPurify HTML sanitization across all dangerouslySetInnerHTML and innerHTML usages. Escape user-controlled values in templates. Set Mermaid securityLevel to strict, KaTeX trust to false. Add useShallow selectors to prevent unnecessary re-renders. Cache loaded images in renderedMarkdown to survive re-renders. Update website download links and changelog. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 97492dd commit 4a36a17

File tree

12 files changed

+224
-84
lines changed

12 files changed

+224
-84
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
Alle nennenswerten Änderungen an diesem Projekt werden hier dokumentiert.
44

5+
## [1.0.22-beta] - 2026-02-08
6+
7+
### Security
8+
- **DOMPurify HTML-Sanitization**: Alle `dangerouslySetInnerHTML`- und `innerHTML`-Ausgaben werden jetzt mit DOMPurify sanitized — verhindert XSS über bösartige Markdown-Dateien, SVGs oder AI-Antworten
9+
- **SVG-Sanitization**: SVG-Dateien im ImageViewer werden mit spezieller SVG-Sanitization gerendert (Script-Tags, Event-Handler und foreignObject werden entfernt)
10+
- **HTML-Escaping**: Alle user-kontrollierten Werte (Dateinamen, Notiz-Namen, Fehlermeldungen) in innerHTML-Templates werden jetzt HTML-escaped
11+
- **Mermaid Security**: `securityLevel` von `loose` auf `strict` geändert — verhindert Click-Callbacks und HTML-Labels in Diagrammen
12+
- **KaTeX Trust**: `trust` von `true` auf `false` geändert — blockiert potenziell gefährliche KaTeX-Befehle
13+
- **Zustand Selector-Optimierung**: `useShallow` für Store-Aufrufe im MarkdownEditor — verhindert unnötige Re-Renders bei Panel-Wechseln
14+
15+
### Fixes
16+
- **Preview-Bilder bei Panel-Wechsel**: Geladene Bilder werden jetzt gecacht und direkt in den HTML-String eingebettet — SVGs/Bilder verschwinden nicht mehr beim Öffnen von Karteikarten oder anderen Panels
17+
518
## [1.0.21-beta] - 2026-02-08
619

720
### Features

app/package-lock.json

Lines changed: 16 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mindgraph-notes",
3-
"version": "1.0.21-beta",
3+
"version": "1.0.22-beta",
44
"description": "Eine minimalistische Markdown-Notizen-App mit editierbarer Graph View",
55
"main": "out/main/index.js",
66
"scripts": {
@@ -80,6 +80,7 @@
8080
}
8181
},
8282
"devDependencies": {
83+
"@types/dompurify": "^3.0.5",
8384
"@types/markdown-it": "^14.1.2",
8485
"@types/react": "^19.2.8",
8586
"@types/react-dom": "^19.2.3",
@@ -100,6 +101,7 @@
100101
"@xterm/addon-fit": "^0.11.0",
101102
"@xterm/xterm": "^6.0.0",
102103
"chokidar": "^5.0.0",
104+
"dompurify": "^3.3.1",
103105
"html-to-image": "^1.11.13",
104106
"katex": "^0.16.27",
105107
"markdown-it": "^14.1.0",

app/src/renderer/components/Editor/MarkdownEditor.tsx

Lines changed: 80 additions & 57 deletions
Large diffs are not rendered by default.

app/src/renderer/components/Editor/extensions/dataview/widget.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { WidgetType } from '@codemirror/view'
77
import type { Note } from '../../../../../shared/types'
88
import { renderResult } from '../../../../utils/dataview'
99
import { useDataviewStore } from '../../../../stores/dataviewStore'
10+
import { sanitizeHtml, escapeHtml } from '../../../../utils/sanitize'
1011

1112
export class DataviewWidget extends WidgetType {
1213
private container: HTMLElement | null = null
@@ -64,7 +65,7 @@ export class DataviewWidget extends WidgetType {
6465
console.log('[DataviewWidget] Rendered HTML length:', html.length)
6566

6667
if (this.container) {
67-
this.container.innerHTML = html
68+
this.container.innerHTML = sanitizeHtml(html)
6869

6970
// Add click handlers for note links
7071
this.setupLinkHandlers()
@@ -74,7 +75,7 @@ export class DataviewWidget extends WidgetType {
7475
if (this.container) {
7576
this.container.innerHTML = `<div class="dataview-error">
7677
<span class="dataview-error-icon">⚠️</span>
77-
<span class="dataview-error-message">Error: ${error instanceof Error ? error.message : 'Unknown error'}</span>
78+
<span class="dataview-error-message">Error: ${escapeHtml(error instanceof Error ? error.message : 'Unknown error')}</span>
7879
</div>`
7980
}
8081
}

app/src/renderer/components/Editor/extensions/livePreview/widgets.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { WidgetType } from '@codemirror/view'
2+
import { escapeHtml } from '../../../../utils/sanitize'
23

34
/**
45
* Widget for rendered wikilinks
@@ -390,7 +391,7 @@ export class PdfEmbedWidget extends WidgetType {
390391
<path d="M4 1C3.45 1 3 1.45 3 2V14C3 14.55 3.45 15 4 15H12C12.55 15 13 14.55 13 14V5.41C13 5.15 12.89 4.9 12.71 4.71L10.29 2.29C10.1 2.11 9.85 2 9.59 2H4Z" fill="#ffebee" stroke="#e53935" strokeWidth="0.8"/>
391392
<text x="8" y="11" text-anchor="middle" font-size="5" font-weight="bold" fill="#e53935">PDF</text>
392393
</svg>
393-
<span class="lp-pdf-filename">${this.filename}</span>
394+
<span class="lp-pdf-filename">${escapeHtml(this.filename)}</span>
394395
`
395396
container.appendChild(header)
396397

@@ -420,7 +421,7 @@ export class PdfEmbedWidget extends WidgetType {
420421
console.log('[PdfEmbed] iframe added to container')
421422
} catch (e) {
422423
console.error('[PdfEmbed] Error creating PDF view:', e)
423-
container.innerHTML += `<div class="lp-pdf-error">Error: ${e}</div>`
424+
container.innerHTML += `<div class="lp-pdf-error">Error: ${escapeHtml(String(e))}</div>`
424425
}
425426
} else {
426427
// PDF not found
@@ -430,7 +431,7 @@ export class PdfEmbedWidget extends WidgetType {
430431
<path d="M4 1C3.45 1 3 1.45 3 2V14C3 14.55 3.45 15 4 15H12C12.55 15 13 14.55 13 14V5.41C13 5.15 12.89 4.9 12.71 4.71L10.29 2.29C10.1 2.11 9.85 2 9.59 2H4Z" fill="#ffebee" stroke="#e53935" strokeWidth="0.8"/>
431432
<text x="8" y="11" text-anchor="middle" font-size="5" font-weight="bold" fill="#e53935">PDF</text>
432433
</svg>
433-
<span>PDF nicht gefunden: ${this.filename}</span>
434+
<span>PDF nicht gefunden: ${escapeHtml(this.filename)}</span>
434435
</div>
435436
`
436437
}

app/src/renderer/components/Flashcards/MarkdownContent.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useEffect, useRef } from 'react'
22
import MarkdownIt from 'markdown-it'
3+
import { sanitizeHtml } from '../../utils/sanitize'
34
import texmath from 'markdown-it-texmath'
45
import katex from 'katex'
56
import 'katex/dist/katex.min.css'
@@ -10,7 +11,7 @@ import mermaid from 'mermaid'
1011
mermaid.initialize({
1112
startOnLoad: false,
1213
theme: 'dark',
13-
securityLevel: 'loose',
14+
securityLevel: 'strict',
1415
fontFamily: 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif'
1516
})
1617

@@ -28,7 +29,7 @@ md.use(texmath, {
2829
delimiters: 'dollars', // $...$ für inline, $$...$$ für block
2930
katexOptions: {
3031
throwOnError: false,
31-
trust: true,
32+
trust: false,
3233
strict: false,
3334
displayMode: false
3435
}
@@ -95,7 +96,7 @@ export const MarkdownContent: React.FC<MarkdownContentProps> = ({ content, class
9596
<div
9697
ref={containerRef}
9798
className={`markdown-content ${className}`}
98-
dangerouslySetInnerHTML={{ __html: html }}
99+
dangerouslySetInnerHTML={{ __html: sanitizeHtml(html) }}
99100
/>
100101
)
101102
}

app/src/renderer/components/ImageViewer/ImageViewer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useState, useEffect, useCallback } from 'react'
2+
import { sanitizeSvg } from '../../utils/sanitize'
23
import './ImageViewer.css'
34

45
interface ImageViewerProps {
@@ -170,7 +171,7 @@ export const ImageViewer: React.FC<ImageViewerProps> = ({ filePath, fileName })
170171
<div
171172
className="image-container svg-container"
172173
style={{ transform: `scale(${zoom / 100})` }}
173-
dangerouslySetInnerHTML={{ __html: svgContent }}
174+
dangerouslySetInnerHTML={{ __html: sanitizeSvg(svgContent) }}
174175
/>
175176
)}
176177
</div>

app/src/renderer/components/NotesChat/NotesChat.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'
2+
import { sanitizeHtml } from '../../utils/sanitize'
23
import { useNotesStore } from '../../stores/notesStore'
34
import { useUIStore } from '../../stores/uiStore'
45
import { useTranslation } from '../../utils/translations'
@@ -474,7 +475,7 @@ export const NotesChat: React.FC<NotesChatProps> = ({ onClose }) => {
474475
<div key={idx} className={`notes-chat-message ${msg.role}`}>
475476
<div
476477
className="notes-chat-message-content markdown-content"
477-
dangerouslySetInnerHTML={{ __html: md.render(msg.content) }}
478+
dangerouslySetInnerHTML={{ __html: sanitizeHtml(md.render(msg.content)) }}
478479
/>
479480
{msg.role === 'assistant' && (
480481
<button
@@ -500,7 +501,7 @@ export const NotesChat: React.FC<NotesChatProps> = ({ onClose }) => {
500501
<div className="notes-chat-message assistant streaming">
501502
<div
502503
className="notes-chat-message-content markdown-content"
503-
dangerouslySetInnerHTML={{ __html: md.render(streamingContent) + '<span class="notes-chat-cursor">|</span>' }}
504+
dangerouslySetInnerHTML={{ __html: sanitizeHtml(md.render(streamingContent)) + '<span class="notes-chat-cursor">|</span>' }}
504505
/>
505506
</div>
506507
)}

app/src/renderer/components/WhatsNew/WhatsNew.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState, useEffect, useMemo } from 'react'
22
import MarkdownIt from 'markdown-it'
3+
import { sanitizeHtml } from '../../utils/sanitize'
34
import { useUIStore } from '../../stores/uiStore'
45
import { useTranslation } from '../../utils/translations'
56

@@ -79,7 +80,7 @@ export const WhatsNew: React.FC = () => {
7980
) : content ? (
8081
<div
8182
className="whats-new-markdown"
82-
dangerouslySetInnerHTML={{ __html: renderedContent }}
83+
dangerouslySetInnerHTML={{ __html: sanitizeHtml(renderedContent) }}
8384
/>
8485
) : (
8586
<div className="whats-new-empty">{t('whatsNew.noContent')}</div>

0 commit comments

Comments
 (0)