diff --git a/electron/ipc/project/manager.ts b/electron/ipc/project/manager.ts index 8d13e832..1ff91c3c 100644 --- a/electron/ipc/project/manager.ts +++ b/electron/ipc/project/manager.ts @@ -90,8 +90,10 @@ export function isAllowedLocalReadPath(candidatePath: string) { ); } -// Keep media-server access rules aligned with read-local-file so exported videos -// saved outside the active recording session can still be reopened in the editor. +// Keep loopback media-server access restricted to allowlisted or explicitly +// approved files. Direct renderer-side read-local-file calls can be more +// permissive, but URL-based serving must stay scoped so arbitrary paths do not +// become fetchable inside the app. export async function isAllowedLocalMediaPath(candidatePath: string) { const normalizedCandidatePath = normalizePath(candidatePath); return isAllowedLocalReadPath(normalizedCandidatePath); diff --git a/electron/ipc/register/assets.ts b/electron/ipc/register/assets.ts index 1749e051..a0b132ea 100644 --- a/electron/ipc/register/assets.ts +++ b/electron/ipc/register/assets.ts @@ -109,6 +109,11 @@ export function registerAssetHandlers() { ipcMain.handle('read-local-file', async (_, filePath: string) => { try { + // Intentionally more permissive than the media-server allowlist: this IPC + // is used for direct renderer-side local file reads after the app has + // already accepted a path, while URL-based media serving must stay scoped + // to approved/app-managed locations. We still canonicalize the path and + // require a real on-disk file so this cannot be used to read directories. const resolved = await resolveReadableLocalFilePath(filePath) const data = await fs.readFile(resolved)