Skip to content

Commit af9c758

Browse files
committed
fix: resolve critical XSS vulnerabilities and code quality issues
- Fix XSS vulnerability in note preview by replacing innerHTML with safe DOMParser - Fix XSS vulnerability in print functionality using secure DOM manipulation - Remove duplicate clear() method in MessageAuthenticator class - Preserve readable spacing in note card previews by handling block elements - Add proper spacing for paragraphs, headings, and line breaks in text extraction - Enhance security by using textContent and DOM methods instead of string injection
1 parent 5e38047 commit af9c758

42 files changed

Lines changed: 2291 additions & 1501 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/components/common/WebSocketStatus.tsx

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { Wifi, WifiOff, RefreshCw, AlertCircle, CheckCircle, Clock } from 'lucide-react';
1+
import {
2+
Wifi,
3+
WifiOff,
4+
RefreshCw,
5+
AlertCircle,
6+
CheckCircle,
7+
Clock,
8+
} from 'lucide-react';
29
import {
310
DropdownMenu,
411
DropdownMenuContent,
@@ -23,7 +30,11 @@ interface WebSocketStatusProps {
2330
showDetails?: boolean;
2431
}
2532

26-
const getStatusIcon = (_status: WebSocketStatus, isConnected: boolean, isAuthenticated: boolean) => {
33+
const getStatusIcon = (
34+
_status: WebSocketStatus,
35+
isConnected: boolean,
36+
isAuthenticated: boolean
37+
) => {
2738
// Keep wifi icons but with proper colors
2839
const isConnectedAndAuth = isConnected && isAuthenticated;
2940

@@ -36,14 +47,16 @@ const getStatusIcon = (_status: WebSocketStatus, isConnected: boolean, isAuthent
3647

3748
const getStatusText = (status: WebSocketStatus, isAuthenticated: boolean) => {
3849
// Simplified: only show Connected or Disconnected
39-
const isConnectedAndAuth = (status === 'connected' || status === 'authenticated') && isAuthenticated;
50+
const isConnectedAndAuth =
51+
(status === 'connected' || status === 'authenticated') && isAuthenticated;
4052

4153
return isConnectedAndAuth ? 'Connected' : 'Disconnected';
4254
};
4355

4456
const getStatusColor = (status: WebSocketStatus, isAuthenticated: boolean) => {
4557
// Simplified: green for connected, gray for disconnected
46-
const isConnectedAndAuth = (status === 'connected' || status === 'authenticated') && isAuthenticated;
58+
const isConnectedAndAuth =
59+
(status === 'connected' || status === 'authenticated') && isAuthenticated;
4760

4861
return isConnectedAndAuth ? 'text-green-600' : 'text-gray-500';
4962
};
@@ -93,25 +106,26 @@ export function WebSocketStatus({
93106
return (
94107
<DropdownMenu>
95108
<DropdownMenuTrigger asChild>
96-
<div className="flex items-center gap-1 px-1.5 py-0.5 hover:bg-muted rounded cursor-default" title={statusText}>
109+
<div
110+
className="hover:bg-muted flex cursor-default items-center gap-1 rounded px-1.5 py-0.5"
111+
title={statusText}
112+
>
97113
{statusIcon}
98-
<span className="hidden sm:inline">
99-
{statusText}
100-
</span>
114+
<span className="hidden sm:inline">{statusText}</span>
101115
</div>
102116
</DropdownMenuTrigger>
103117
<DropdownMenuContent align="end" className="w-64">
104118
<div className="p-2">
105-
<div className="flex items-center gap-2 mb-2">
119+
<div className="mb-2 flex items-center gap-2">
106120
{statusIcon}
107121
<span className={`text-sm font-medium ${statusColor}`}>
108122
{statusText}
109123
</span>
110124
</div>
111125

112126
{error && (
113-
<div className="mb-2 p-2 bg-red-50 border border-red-200 rounded text-xs text-red-700">
114-
<div className="font-medium mb-1">Error:</div>
127+
<div className="mb-2 rounded border border-red-200 bg-red-50 p-2 text-xs text-red-700">
128+
<div className="mb-1 font-medium">Error:</div>
115129
<div>{error}</div>
116130
</div>
117131
)}
@@ -187,20 +201,19 @@ export function WebSocketStatusCompact({
187201
isAuthenticated,
188202
lastSync,
189203
}: Pick<WebSocketStatusProps, 'status' | 'isAuthenticated' | 'lastSync'>) {
190-
const isConnectedAndAuth = (status === 'connected' || status === 'authenticated') && isAuthenticated;
204+
const isConnectedAndAuth =
205+
(status === 'connected' || status === 'authenticated') && isAuthenticated;
191206
const statusIcon = getStatusIcon(status, isConnectedAndAuth, isAuthenticated);
192207
const statusText = isConnectedAndAuth ? 'Connected' : 'Disconnected';
193208

194209
return (
195210
<div className="flex items-center gap-1">
196211
<div
197-
className="flex items-center gap-1 px-1.5 py-0.5 rounded cursor-default"
212+
className="flex cursor-default items-center gap-1 rounded px-1.5 py-0.5"
198213
title={`${statusText}${lastSync ? ` • Last sync: ${formatLastSync(lastSync)}` : ''}`}
199214
>
200215
{statusIcon}
201-
<span className="hidden sm:inline">
202-
{statusText}
203-
</span>
216+
<span className="hidden sm:inline">{statusText}</span>
204217
</div>
205218
</div>
206219
);
@@ -209,15 +222,15 @@ export function WebSocketStatusCompact({
209222
// Sync indicator for showing when sync is in progress
210223
export function SyncIndicator({
211224
isVisible,
212-
message = 'Syncing...'
225+
message = 'Syncing...',
213226
}: {
214227
isVisible: boolean;
215228
message?: string;
216229
}) {
217230
if (!isVisible) return null;
218231

219232
return (
220-
<div className="flex items-center gap-2 px-2 py-1 bg-blue-50 border border-blue-200 rounded text-xs text-blue-700">
233+
<div className="flex items-center gap-2 rounded border border-blue-200 bg-blue-50 px-2 py-1 text-xs text-blue-700">
221234
<RefreshCw className="h-3 w-3 animate-spin" />
222235
<span>{message}</span>
223236
</div>
@@ -251,15 +264,17 @@ export function SyncNotification({
251264
};
252265

253266
return (
254-
<div className={`fixed top-4 right-4 z-50 flex items-center gap-2 px-3 py-2 border rounded shadow-sm ${colors[type]} max-w-sm`}>
267+
<div
268+
className={`fixed top-4 right-4 z-50 flex items-center gap-2 rounded border px-3 py-2 shadow-sm ${colors[type]} max-w-sm`}
269+
>
255270
{icons[type]}
256-
<span className="text-sm flex-1">{message}</span>
271+
<span className="flex-1 text-sm">{message}</span>
257272
<button
258273
onClick={onDismiss}
259-
className="text-gray-500 hover:text-gray-700 ml-2"
274+
className="ml-2 text-gray-500 hover:text-gray-700"
260275
>
261276
×
262277
</button>
263278
</div>
264279
);
265-
}
280+
}

src/components/editor/Editor/StatusBar.tsx

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,68 +39,77 @@ export function StatusBar({
3939
onWsReconnect,
4040
}: StatusBarProps) {
4141
return (
42-
<div className="border-t bg-primary/5 dark:bg-primary/10 px-3 py-0.5 flex items-center justify-between text-[11px] font-sans">
42+
<div className="bg-primary/5 dark:bg-primary/10 flex items-center justify-between border-t px-3 py-0.5 font-sans text-[11px]">
4343
{/* Left side - Word, character count, and reading time */}
4444
<div className="flex items-center gap-3">
4545
{(wordCount > 0 || charCount > 0) && (
4646
<>
47-
<span className="hover:bg-muted px-1.5 py-0.5 rounded cursor-default">
47+
<span className="hover:bg-muted cursor-default rounded px-1.5 py-0.5">
4848
{wordCount} {wordCount === 1 ? 'word' : 'words'}
4949
</span>
50-
<span className="hover:bg-muted px-1.5 py-0.5 rounded cursor-default">
50+
<span className="hover:bg-muted cursor-default rounded px-1.5 py-0.5">
5151
{charCount} {charCount === 1 ? 'character' : 'characters'}
5252
</span>
5353
</>
5454
)}
5555
{readingTime > 0 && (
56-
<span className="hover:bg-muted px-1.5 py-0.5 rounded cursor-default" title="Estimated reading time">
56+
<span
57+
className="hover:bg-muted cursor-default rounded px-1.5 py-0.5"
58+
title="Estimated reading time"
59+
>
5760
~{readingTime} min read
5861
</span>
5962
)}
6063
</div>
61-
64+
6265
{/* Right side - Scroll percentage, zoom controls, and save status */}
6366
<div className="flex items-center gap-3">
6467
{scrollPercentage > 0 && (
65-
<span className="hover:bg-muted px-1.5 py-0.5 rounded cursor-default" title="Scroll position">
68+
<span
69+
className="hover:bg-muted cursor-default rounded px-1.5 py-0.5"
70+
title="Scroll position"
71+
>
6672
{scrollPercentage}%
6773
</span>
6874
)}
69-
75+
7076
{/* Note ID */}
7177
{noteId && (
72-
<span className="hover:bg-muted px-1.5 py-0.5 rounded cursor-default" title="Note ID">
78+
<span
79+
className="hover:bg-muted cursor-default rounded px-1.5 py-0.5"
80+
title="Note ID"
81+
>
7382
{noteId}
7483
</span>
7584
)}
76-
85+
7786
{/* Zoom controls */}
7887
<div className="flex items-center gap-1">
7988
<button
8089
onClick={onZoomOut}
81-
className="hover:bg-muted p-1 rounded cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
90+
className="hover:bg-muted text-muted-foreground hover:text-foreground cursor-pointer rounded p-1 transition-colors"
8291
title="Zoom out"
8392
disabled={zoomLevel <= 50}
8493
>
8594
<Minus className="h-3 w-3" />
8695
</button>
87-
<span
88-
className="hover:bg-muted px-1.5 py-0.5 rounded cursor-pointer min-w-[45px] text-center"
96+
<span
97+
className="hover:bg-muted min-w-[45px] cursor-pointer rounded px-1.5 py-0.5 text-center"
8998
onClick={onResetZoom}
9099
title="Click to reset zoom"
91100
>
92101
{zoomLevel}%
93102
</span>
94103
<button
95104
onClick={onZoomIn}
96-
className="hover:bg-muted p-1 rounded cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
105+
className="hover:bg-muted text-muted-foreground hover:text-foreground cursor-pointer rounded p-1 transition-colors"
97106
title="Zoom in"
98107
disabled={zoomLevel >= 200}
99108
>
100109
<Plus className="h-3 w-3" />
101110
</button>
102111
</div>
103-
112+
104113
{/* WebSocket sync status */}
105114
{wsStatus && onWsReconnect && (
106115
<WebSocketStatusCompact
@@ -111,14 +120,19 @@ export function StatusBar({
111120
)}
112121

113122
{/* Save status */}
114-
<div className="flex items-center gap-1 px-1.5 py-0.5 hover:bg-muted rounded cursor-default" title={
115-
saveStatus === 'saving' ? 'Saving changes...' :
116-
saveStatus === 'saved' ? 'All changes saved' :
117-
'Error saving changes'
118-
}>
123+
<div
124+
className="hover:bg-muted flex cursor-default items-center gap-1 rounded px-1.5 py-0.5"
125+
title={
126+
saveStatus === 'saving'
127+
? 'Saving changes...'
128+
: saveStatus === 'saved'
129+
? 'All changes saved'
130+
: 'Error saving changes'
131+
}
132+
>
119133
{saveStatus === 'saving' && (
120134
<>
121-
<div className="h-1.5 w-1.5 rounded-full bg-orange-500 animate-pulse" />
135+
<div className="h-1.5 w-1.5 animate-pulse rounded-full bg-orange-500" />
122136
<span>Saving</span>
123137
</>
124138
)}
@@ -138,4 +152,4 @@ export function StatusBar({
138152
</div>
139153
</div>
140154
);
141-
}
155+
}

0 commit comments

Comments
 (0)