Improve attachment memory safety, downloads, and high-memory alert UX.
All checks were successful
Queue Release Build / prepare (push) Successful in 20s
Deploy Web Apps / deploy (push) Successful in 9m2s
Queue Release Build / build-windows (push) Successful in 28m8s
Queue Release Build / build-linux (push) Successful in 47m26s
Queue Release Build / build-android (push) Successful in 19m52s
Queue Release Build / finalize (push) Successful in 4m42s

Stream large receives to disk with chunk acks to cap renderer RAM, evict
off-screen display blobs, and route exports through a disk-aware download
service. Fix the high-memory dialog (backdrop dismiss, copy, log actions),
allow diagnostics paths in the path jail, and restore persisted image
hydration after reload.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 00:25:22 +02:00
parent f0d79aa627
commit bb0ac930ad
69 changed files with 2306 additions and 430 deletions

View File

@@ -0,0 +1,16 @@
import {
describe,
expect,
it
} from 'vitest';
import { isReadableRegularFile } from './file-read.rules';
describe('file-read.rules', () => {
it('accepts regular files', () => {
expect(isReadableRegularFile({ isFile: () => true })).toBe(true);
});
it('rejects directories and other non-file paths', () => {
expect(isReadableRegularFile({ isFile: () => false })).toBe(false);
});
});

View File

@@ -0,0 +1,6 @@
import type { Stats } from 'fs';
/** Only regular files can be read through the read-file IPC surface. */
export function isReadableRegularFile(stats: Pick<Stats, 'isFile'>): boolean {
return stats.isFile();
}

View File

@@ -68,6 +68,7 @@ import {
grantPluginReadRoot,
resolveReadablePath
} from '../path-jail';
import { isReadableRegularFile } from './file-read.rules';
const DEFAULT_MIME_TYPE = 'application/octet-stream';
const MAX_ACTIVE_DESKTOP_NOTIFICATIONS = 20;
@@ -654,9 +655,19 @@ export function setupSystemHandlers(): void {
return null;
}
const data = await fsp.readFile(scopedPath);
try {
const stats = await fsp.stat(scopedPath);
return data.toString('base64');
if (!isReadableRegularFile(stats)) {
return null;
}
const data = await fsp.readFile(scopedPath);
return data.toString('base64');
} catch {
return null;
}
});
ipcMain.handle('read-file-chunk', async (_event, filePath: string, start: number, end: number) => {
@@ -666,17 +677,27 @@ export function setupSystemHandlers(): void {
return null;
}
const fileHandle = await fsp.open(scopedPath, 'r');
try {
const safeStart = Math.max(0, Math.trunc(start));
const safeEnd = Math.max(safeStart, Math.trunc(end));
const buffer = Buffer.alloc(safeEnd - safeStart);
const result = await fileHandle.read(buffer, 0, buffer.length, safeStart);
const stats = await fsp.stat(scopedPath);
return buffer.subarray(0, result.bytesRead).toString('base64');
} finally {
await fileHandle.close();
if (!isReadableRegularFile(stats)) {
return null;
}
const fileHandle = await fsp.open(scopedPath, 'r');
try {
const safeStart = Math.max(0, Math.trunc(start));
const safeEnd = Math.max(safeStart, Math.trunc(end));
const buffer = Buffer.alloc(safeEnd - safeStart);
const result = await fileHandle.read(buffer, 0, buffer.length, safeStart);
return buffer.subarray(0, result.bytesRead).toString('base64');
} finally {
await fileHandle.close();
}
} catch {
return null;
}
});
@@ -728,6 +749,17 @@ export function setupSystemHandlers(): void {
return true;
});
ipcMain.handle('append-file-bytes', async (_event, filePath: string, bytes: Uint8Array) => {
const scopedPath = await resolveWritableUserDataFilePath(filePath);
if (!scopedPath) {
return false;
}
await fsp.appendFile(scopedPath, Buffer.from(bytes));
return true;
});
ipcMain.handle('delete-file', async (_event, filePath: string) => {
const scopedPath = await resolveWritableUserDataFilePath(filePath);