-
Notifications
You must be signed in to change notification settings - Fork 173
fix: add file type validation to Tesseract OCR processing #778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -35,6 +35,24 @@ export interface VideoProgress { | |||||||||||||||
|
|
||||||||||||||||
| export type ProgressReporter = (update: VideoProgress) => Promise<void> | 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<void> { | ||||||||||||||||
| try { | ||||||||||||||||
| const response = await axios.head(url, { timeout: 5000 }); | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Map the target file and nearby symbols first.
ast-grep outline src/lib/video/pipeline.ts --view expanded || true
# Show the relevant section with line numbers.
sed -n '1,220p' src/lib/video/pipeline.ts | cat -n
# Search for the event payload source and any URL validation/allow-listing helpers.
rg -n "video/generate\.requested|imageUrl|axios\.head|Tesseract\.recognize|allow-list|private|link-local|https?://" src/lib/video -S || trueRepository: knoxiboy/DoubtDesk Length of output: 10882 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find the event handler and any request schema that populates `imageUrl`.
rg -n "video/generate\.requested|runVideoPipeline\(|imageUrl" . -S
# Inspect the likely event consumer / API entrypoints if present.
fd -H -t f ".*(api|inngest|event|video).*" . || trueRepository: knoxiboy/DoubtDesk Length of output: 10917 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate the file that defines the event/request flow if search results are manageable.
rg -n "video/generate\.requested|runVideoPipeline\(" src app api lib -S || trueRepository: knoxiboy/DoubtDesk Length of output: 744 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the video validation and the shared `safeUrl` definition.
sed -n '1,220p' src/lib/validations/video.ts | cat -n
printf '\n----\n'
rg -n "const safeUrl|safeUrl\s*=|safeUrl:" src/lib -S
printf '\n----\n'
# Read the file that defines safeUrl if found.
fd -H -t f ".*validations.*" src/lib | sortRepository: knoxiboy/DoubtDesk Length of output: 678 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate and inspect the shared URL validator.
rg -n "safeUrl" src -SRepository: knoxiboy/DoubtDesk Length of output: 1004 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Read the request/event flow around the video generation endpoint and Inngest consumer.
sed -n '1,140p' src/app/api/video/generate/route.ts | cat -n
printf '\n----\n'
sed -n '330,380p' src/inngest/functions.ts | cat -n
printf '\n----\n'
sed -n '1,120p' src/lib/validations/common.ts | cat -nRepository: knoxiboy/DoubtDesk Length of output: 6716 Restrict server-side image fetches 🤖 Prompt for AI Agents |
||||||||||||||||
| const contentType = String(response.headers["content-type"] || "").toLowerCase(); | ||||||||||||||||
|
|
||||||||||||||||
| if (!ALLOWED_IMAGE_TYPES.some(type => contentType.includes(type))) { | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Substring match on content-type is weak.
♻️ Suggested tightening- const contentType = String(response.headers["content-type"] || "").toLowerCase();
-
- if (!ALLOWED_IMAGE_TYPES.some(type => contentType.includes(type))) {
+ const contentType = String(response.headers["content-type"] || "")
+ .toLowerCase()
+ .split(";")[0]
+ .trim();
+
+ if (!ALLOWED_IMAGE_TYPES.includes(contentType)) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| throw new Error(`Invalid file type: ${contentType || "unknown"}. Only image files are allowed.`); | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+45
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The MIME-type check uses substring matching, so crafted or malformed Severity Level: Major
|
||||||||||||||||
| } 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"); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
|
Comment on lines
+38
to
+55
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== file list ==\n'
git ls-files src/lib/video/pipeline.ts
printf '\n== outline ==\n'
ast-grep outline src/lib/video/pipeline.ts --view expanded || true
printf '\n== relevant slices ==\n'
sed -n '1,220p' src/lib/video/pipeline.ts | cat -nRepository: knoxiboy/DoubtDesk Length of output: 10370 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== search for validateImageUrl and Tesseract.recognize usages ==\n'
rg -n "validateImageUrl|Tesseract\.recognize|content-type|ALLOWED_IMAGE_TYPES|axios\.head|axios\.get" src lib . -g '!node_modules' || trueRepository: knoxiboy/DoubtDesk Length of output: 2631 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== surrounding lines near reported locations ==\n'
sed -n '30,150p' src/lib/video/pipeline.ts | cat -n
printf '\n== tail slice ==\n'
sed -n '150,320p' src/lib/video/pipeline.ts | cat -nRepository: knoxiboy/DoubtDesk Length of output: 11358 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== git diff stat for pipeline file ==\n'
git diff --stat -- src/lib/video/pipeline.ts || true
printf '\n== file size ==\n'
wc -l src/lib/video/pipeline.tsRepository: knoxiboy/DoubtDesk Length of output: 242 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== exact call flow from pipeline.ts ==\n'
python3 - <<'PY'
from pathlib import Path
p=Path('src/lib/video/pipeline.ts')
text=p.read_text()
for needle in ['validateImageUrl(', 'Tesseract.recognize(', 'axios.head(', 'axios.get(']:
print(f"\n--- {needle} ---")
for i,line in enumerate(text.splitlines(),1):
if needle in line:
start=max(1,i-8); end=min(len(text.splitlines()), i+12)
for j in range(start,end+1):
print(f"{j:4d}: {text.splitlines()[j-1]}")
break
PYRepository: knoxiboy/DoubtDesk Length of output: 3133 Validate the OCR input bytes, not the HEAD header 🤖 Prompt for AI Agents |
||||||||||||||||
| export async function cleanupVideoArtifacts(tempDir: string, outputLocation: string): Promise<void> { | ||||||||||||||||
| 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"); | ||||||||||||||||
|
|
||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: The new URL validation performs a server-side request to a user-controlled URL without any private-network or host allowlist checks, which enables SSRF against internal services. Validate and reject localhost/private IP ranges (and optionally enforce an allowlist) before making the outbound request. [ssrf]
Severity Level: Critical 🚨
Steps of Reproduction ✅
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖