Skip to content

Commit 7ebc07f

Browse files
committed
Security fixes and Plugin Guidelines compliance - v1.3.1
CRITICAL FIXES: - Add escapeHtml() to prevent XSS attacks from user content - Escape all user input in innerHTML (file names, note content) - Remove detachLeavesOfType() from onunload() per Obsidian guidelines CHANGES: - Version bump to 1.3.1 - Security hardening for user-generated content display - Improved resource management during plugin unload Complies with Obsidian Plugin Guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines
1 parent f8d3f42 commit 7ebc07f

File tree

4 files changed

+29
-9
lines changed

4 files changed

+29
-9
lines changed

main.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -847,15 +847,19 @@ var DashReaderView = class extends import_obsidian.ItemView {
847847
</div>
848848
`;
849849
}
850+
escapeHtml(text) {
851+
return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
852+
}
850853
processWord(word) {
851854
const cleanWord = word.trim();
852855
const center = Math.max(Math.floor(cleanWord.length / 2) - 1, 0);
853856
let result = "";
854857
for (let i = 0; i < cleanWord.length; i++) {
858+
const escapedChar = this.escapeHtml(cleanWord[i]);
855859
if (i === center) {
856-
result += `<span class="dashreader-highlight" style="color: ${this.settings.highlightColor}">${cleanWord[i]}</span>`;
860+
result += `<span class="dashreader-highlight" style="color: ${this.settings.highlightColor}">${escapedChar}</span>`;
857861
} else {
858-
result += cleanWord[i];
862+
result += escapedChar;
859863
}
860864
}
861865
return result;
@@ -925,8 +929,10 @@ var DashReaderView = class extends import_obsidian.ItemView {
925929
this.wordEl.innerHTML = "";
926930
let sourceInfo = "";
927931
if (source == null ? void 0 : source.fileName) {
932+
const escapedFileName = this.escapeHtml(source.fileName);
933+
const lineInfo = source.lineNumber ? ` (line ${source.lineNumber})` : "";
928934
sourceInfo = `<div style="font-size: 14px; opacity: 0.6; margin-bottom: 8px;">
929-
\u{1F4C4} ${source.fileName}${source.lineNumber ? ` (line ${source.lineNumber})` : ""}
935+
\u{1F4C4} ${escapedFileName}${lineInfo}
930936
</div>`;
931937
}
932938
const totalWords = this.engine.getTotalWords();
@@ -1205,7 +1211,6 @@ var DashReaderPlugin = class extends import_obsidian3.Plugin {
12051211
}
12061212
onunload() {
12071213
console.log("Unloading DashReader plugin");
1208-
this.app.workspace.detachLeavesOfType(VIEW_TYPE_DASHREADER);
12091214
}
12101215
async loadSettings() {
12111216
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());

main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export default class DashReaderPlugin extends Plugin {
120120

121121
onunload() {
122122
console.log('Unloading DashReader plugin');
123-
this.app.workspace.detachLeavesOfType(VIEW_TYPE_DASHREADER);
123+
// Don't detach leaves here - let Obsidian restore them at original positions during updates
124124
}
125125

126126
async loadSettings() {

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "dashreader",
33
"name": "DashReader",
4-
"version": "1.3.0",
4+
"version": "1.3.1",
55
"minAppVersion": "0.15.0",
66
"description": "Speed reading with RSVP (Rapid Serial Visual Presentation) - Read faster, understand better",
77
"author": "inattendu",

src/rsvp-view.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -673,17 +673,29 @@ export class DashReaderView extends ItemView {
673673
`;
674674
}
675675

676+
private escapeHtml(text: string): string {
677+
// Escape HTML characters to prevent XSS attacks
678+
return text.replace(/&/g, '&amp;')
679+
.replace(/</g, '&lt;')
680+
.replace(/>/g, '&gt;')
681+
.replace(/"/g, '&quot;')
682+
.replace(/'/g, '&#039;');
683+
}
684+
676685
private processWord(word: string): string {
677686
// Trouver le centre du mot pour le highlighter
678687
const cleanWord = word.trim();
679688
const center = Math.max(Math.floor(cleanWord.length / 2) - 1, 0);
680689

681690
let result = '';
682691
for (let i = 0; i < cleanWord.length; i++) {
692+
// Escape each character to prevent XSS from user notes
693+
const escapedChar = this.escapeHtml(cleanWord[i]);
694+
683695
if (i === center) {
684-
result += `<span class="dashreader-highlight" style="color: ${this.settings.highlightColor}">${cleanWord[i]}</span>`;
696+
result += `<span class="dashreader-highlight" style="color: ${this.settings.highlightColor}">${escapedChar}</span>`;
685697
} else {
686-
result += cleanWord[i];
698+
result += escapedChar;
687699
}
688700
}
689701

@@ -777,8 +789,11 @@ export class DashReaderView extends ItemView {
777789
// Préparer le message avec le nom du document et ligne
778790
let sourceInfo = '';
779791
if (source?.fileName) {
792+
// Escape filename to prevent XSS if user has malicious filename
793+
const escapedFileName = this.escapeHtml(source.fileName);
794+
const lineInfo = source.lineNumber ? ` (line ${source.lineNumber})` : '';
780795
sourceInfo = `<div style="font-size: 14px; opacity: 0.6; margin-bottom: 8px;">
781-
📄 ${source.fileName}${source.lineNumber ? ` (line ${source.lineNumber})` : ''}
796+
📄 ${escapedFileName}${lineInfo}
782797
</div>`;
783798
}
784799

0 commit comments

Comments
 (0)