diff --git a/src/lib/video/pipeline.ts b/src/lib/video/pipeline.ts index cf11cfab..d6f78acb 100644 --- a/src/lib/video/pipeline.ts +++ b/src/lib/video/pipeline.ts @@ -35,6 +35,24 @@ export interface VideoProgress { export type ProgressReporter = (update: VideoProgress) => Promise | void; +const ALLOWED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/gif", "image/webp", "image/bmp", "image/svg+xml", "image/tiff"]; + +async function validateImageUrl(url: string): Promise { + try { + const response = await axios.head(url, { timeout: 5000 }); + const contentType = String(response.headers["content-type"] || "").toLowerCase(); + + if (!ALLOWED_IMAGE_TYPES.some(type => contentType.includes(type))) { + throw new Error(`Invalid file type: ${contentType || "unknown"}. Only image files are allowed.`); + } + } catch (error) { + if (error instanceof Error && error.message.startsWith("Invalid file type")) { + throw error; + } + throw new Error("Failed to validate image file: unable to fetch file headers"); + } +} + export async function cleanupVideoArtifacts(tempDir: string, outputLocation: string): Promise { await Promise.all([ fs.promises.unlink(outputLocation).catch(() => {}), @@ -109,6 +127,7 @@ export async function runVideoPipeline( // 1. OCR if an image is provided and no text content was supplied. if (imageUrl && !content) { await onProgress({ progress: 10, step: "Reading image (OCR)…" }); + await validateImageUrl(imageUrl); const { data: { text }, } = await Tesseract.recognize(imageUrl, "eng");