Fix security and reliability issues
1. Validate URL scheme in open-external-url handler - Prevent opening file:// or other dangerous schemes via shell.openExternal - Only allow http:, https:, and mailto: protocols 2. Fix latest video detection using mtime instead of lexicographic sort - Lexicographic sort gives wrong results (e.g. recording-9 > recording-10) - Now sorts by file modification time for reliable latest-file detection 3. Add null guard for AudioData.format in cloneWithTimestamp - Replace non-null assertion (!) with proper validation - Throws descriptive error if format is unexpectedly null 4. Prevent encodeQueue counter underflow in VideoExporter - Use Math.max(0, ...) to prevent negative queue count Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -490,7 +490,24 @@ export function registerIpcHandlers(
|
||||
return { success: false, message: "No recorded video found" };
|
||||
}
|
||||
|
||||
const latestVideo = videoFiles.sort().reverse()[0];
|
||||
// Sort by most recently modified to reliably get the latest recording.
|
||||
// Lexicographic sort is unreliable (e.g. recording-9.webm > recording-10.webm).
|
||||
let latestVideo: string | null = null;
|
||||
let latestMtimeMs = 0;
|
||||
for (const file of videoFiles) {
|
||||
try {
|
||||
const stat = await fs.stat(path.join(RECORDINGS_DIR, file));
|
||||
if (stat.mtimeMs > latestMtimeMs) {
|
||||
latestMtimeMs = stat.mtimeMs;
|
||||
latestVideo = file;
|
||||
}
|
||||
} catch {
|
||||
// Skip inaccessible files.
|
||||
}
|
||||
}
|
||||
if (!latestVideo) {
|
||||
return { success: false, message: "No recorded video found" };
|
||||
}
|
||||
const videoPath = path.join(RECORDINGS_DIR, latestVideo);
|
||||
|
||||
return { success: true, path: videoPath };
|
||||
@@ -616,6 +633,18 @@ export function registerIpcHandlers(
|
||||
|
||||
ipcMain.handle("open-external-url", async (_, url: string) => {
|
||||
try {
|
||||
const ALLOWED_SCHEMES = ["http:", "https:", "mailto:"];
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(url);
|
||||
} catch {
|
||||
return { success: false, error: "Invalid URL" };
|
||||
}
|
||||
|
||||
if (!ALLOWED_SCHEMES.includes(parsed.protocol)) {
|
||||
return { success: false, error: `Unsupported URL scheme: ${parsed.protocol}` };
|
||||
}
|
||||
|
||||
await shell.openExternal(url);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
@@ -459,7 +459,10 @@ export class AudioProcessor {
|
||||
}
|
||||
|
||||
private cloneWithTimestamp(src: AudioData, newTimestamp: number): AudioData {
|
||||
const isPlanar = src.format?.includes("planar") ?? false;
|
||||
if (!src.format) {
|
||||
throw new Error("AudioData format is required for cloning");
|
||||
}
|
||||
const isPlanar = src.format.includes("planar");
|
||||
const numPlanes = isPlanar ? src.numberOfChannels : 1;
|
||||
|
||||
let totalSize = 0;
|
||||
@@ -476,7 +479,7 @@ export class AudioProcessor {
|
||||
}
|
||||
|
||||
return new AudioData({
|
||||
format: src.format!,
|
||||
format: src.format,
|
||||
sampleRate: src.sampleRate,
|
||||
numberOfFrames: src.numberOfFrames,
|
||||
numberOfChannels: src.numberOfChannels,
|
||||
|
||||
@@ -422,7 +422,7 @@ export class VideoExporter {
|
||||
})();
|
||||
|
||||
this.muxingPromises.push(muxingPromise);
|
||||
this.encodeQueue--;
|
||||
this.encodeQueue = Math.max(0, this.encodeQueue - 1);
|
||||
},
|
||||
error: (error) => {
|
||||
console.error("[VideoExporter] Encoder error:", error);
|
||||
|
||||
Reference in New Issue
Block a user