Skip to content

Commit ce8a9ee

Browse files
liangweifengclaude
andcommitted
security: add CSP, navigation lockdown, remove bypassCSP
Electron security audit found 3 high-severity issues: **Content Security Policy (HIGH)**: - No CSP existed — renderer could load arbitrary external scripts. - Added meta CSP: `default-src 'self'`, allows `connect-src https: wss:` for API calls, `img-src/media-src data: media:` for local media, `style-src 'unsafe-inline'` for Tailwind. **Navigation lockdown (HIGH)**: - No `will-navigate` or `setWindowOpenHandler` — renderer could navigate to malicious URLs. Added `hardenWindow()` to both main and overlay windows: blocks foreign navigation, opens external links in system browser via `shell.openExternal()`. **media:// bypassCSP (HIGH)**: - Custom protocol had `bypassCSP: true`, which would nullify CSP for media resources. Removed — media files don't need to bypass CSP. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 38b98a4 commit ce8a9ee

3 files changed

Lines changed: 22 additions & 2 deletions

File tree

electron/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function setupAppMenu() {
6868

6969
protocol.registerSchemesAsPrivileged([{
7070
scheme: 'media',
71-
privileges: { secure: true, supportFetchAPI: true, stream: true, bypassCSP: true },
71+
privileges: { secure: true, supportFetchAPI: true, stream: true },
7272
}]);
7373

7474
// ─── Single Instance Lock ──────────────────────────────────────────────────

electron/window-manager.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
1-
import { BrowserWindow, screen } from 'electron';
1+
import { BrowserWindow, screen, shell } from 'electron';
22
import path from 'path';
33
import { state, isDev, isMac } from './app-state';
44

5+
/** Lock down a window: block navigation to foreign URLs, open external links in system browser */
6+
function hardenWindow(win: BrowserWindow) {
7+
// Block in-app navigation to foreign origins
8+
win.webContents.on('will-navigate', (e, url) => {
9+
const allowed = isDev ? 'http://localhost:5173' : `file://${path.join(__dirname, '..')}`;
10+
if (!url.startsWith(allowed)) {
11+
e.preventDefault();
12+
shell.openExternal(url);
13+
}
14+
});
15+
// Block new window creation; open in system browser instead
16+
win.webContents.setWindowOpenHandler(({ url }) => {
17+
shell.openExternal(url);
18+
return { action: 'deny' };
19+
});
20+
}
21+
522
export function createMainWindow() {
623
state.mainWindow = new BrowserWindow({
724
width: 1000,
@@ -26,6 +43,7 @@ export function createMainWindow() {
2643
state.mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
2744
}
2845

46+
hardenWindow(state.mainWindow);
2947
state.mainWindow.once('ready-to-show', () => state.mainWindow?.show());
3048

3149
state.mainWindow.on('close', (e) => {
@@ -71,5 +89,6 @@ export function createOverlayWindow() {
7189
state.overlayWindow.loadFile(path.join(__dirname, '../dist/index.html'), { hash: '/overlay' });
7290
}
7391

92+
hardenWindow(state.overlayWindow);
7493
state.overlayWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
7594
}

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: media:; media-src 'self' media:; connect-src 'self' https: wss:; font-src 'self'" />
67
<title>OpenType</title>
78
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
89
</head>

0 commit comments

Comments
 (0)