mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 23:35:43 +00:00
fix(media): restrict loopback approvals to supported files
This commit is contained in:
@@ -87,4 +87,17 @@ describe("local media path policy", () => {
|
||||
await expect(resolveApprovedLocalMediaPath(videoPath)).resolves.toBe(videoPath);
|
||||
expect(isAllowedMediaPath(videoPath)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects existing non-media files when resolving local media URLs", async () => {
|
||||
const downloadsPath = path.join(tempRoot, "Downloads");
|
||||
const textPath = path.join(downloadsPath, "notes.txt");
|
||||
await fs.mkdir(downloadsPath, { recursive: true });
|
||||
await fs.writeFile(textPath, "not media");
|
||||
|
||||
const { resolveApprovedLocalMediaPath } = await import("./manager");
|
||||
const { isAllowedMediaPath } = await import("../../mediaServer");
|
||||
|
||||
await expect(resolveApprovedLocalMediaPath(textPath)).resolves.toBeNull();
|
||||
expect(isAllowedMediaPath(textPath)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { app } from "electron";
|
||||
import { RECORDINGS_DIR, USER_DATA_PATH } from "../../appPaths";
|
||||
import { isSupportedLocalMediaPath } from "../../mediaTypes";
|
||||
import {
|
||||
PROJECT_FILE_EXTENSION,
|
||||
LEGACY_PROJECT_FILE_EXTENSIONS,
|
||||
@@ -91,6 +92,11 @@ export async function resolveApprovedLocalMediaPath(candidatePath: string): Prom
|
||||
return null;
|
||||
}
|
||||
|
||||
const stat = await fs.stat(realPath).catch(() => null);
|
||||
if (!stat?.isFile() || !isSupportedLocalMediaPath(realPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(await isAllowedLocalMediaPath(realPath))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-18
@@ -3,28 +3,11 @@ import fs from "node:fs/promises";
|
||||
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
||||
import path from "node:path";
|
||||
import { approvedLocalReadPaths } from "./ipc/state";
|
||||
|
||||
const MEDIA_MIME_TYPES: Record<string, string> = {
|
||||
".mp4": "video/mp4",
|
||||
".webm": "video/webm",
|
||||
".mov": "video/quicktime",
|
||||
".mkv": "video/x-matroska",
|
||||
".avi": "video/x-msvideo",
|
||||
".wav": "audio/wav",
|
||||
".mp3": "audio/mpeg",
|
||||
".ogg": "audio/ogg",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
};
|
||||
import { getMediaContentType } from "./mediaTypes";
|
||||
|
||||
let mediaServerBaseUrl: string | null = null;
|
||||
let mediaServerStartPromise: Promise<string> | null = null;
|
||||
|
||||
function getMediaContentType(filePath: string): string {
|
||||
return MEDIA_MIME_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
||||
}
|
||||
|
||||
async function resolveRealPath(filePath: string): Promise<string | null> {
|
||||
try {
|
||||
return await fs.realpath(path.resolve(filePath));
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import path from "node:path";
|
||||
|
||||
export const MEDIA_CONTENT_TYPES: Record<string, string> = {
|
||||
".mp4": "video/mp4",
|
||||
".webm": "video/webm",
|
||||
".mov": "video/quicktime",
|
||||
".mkv": "video/x-matroska",
|
||||
".avi": "video/x-msvideo",
|
||||
".wav": "audio/wav",
|
||||
".mp3": "audio/mpeg",
|
||||
".ogg": "audio/ogg",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
};
|
||||
|
||||
export function getMediaContentType(filePath: string): string {
|
||||
return MEDIA_CONTENT_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
||||
}
|
||||
|
||||
export function isSupportedLocalMediaPath(filePath: string): boolean {
|
||||
return path.extname(filePath).toLowerCase() in MEDIA_CONTENT_TYPES;
|
||||
}
|
||||
Reference in New Issue
Block a user