T09 · Insecure Skill Coding Practices
- Location
scripts/video_gen.js:196- Finding
Arbitrary Local File Upload Through Image Input
- Content
View full analysis
Vulnerability Details
File Location:
scripts/video_gen.js, lines 196–245 and 273–283
Vulnerability Type: Arbitrary readable-file disclosure to an external service
Risk Level: HighVulnerable Code
js function inferMimeType(filePath) { return IMAGE_MIME_TYPES[path.extname(filePath).toLowerCase()] || 'application/octet-stream'; } async function uploadFileToPublicUrl(inputPath, apiKey) { const resolvedPath = normalizeLocalFilePath(inputPath); if (!resolvedPath) { throw new Error(`Invalid local image path: ${inputPath}`); } const filePath = resolvedPath instanceof URL ? fileURLToPath(resolvedPath) : path.resolve(resolvedPath); let stat; try { stat = await fs.stat(filePath); } catch { throw new Error(`Local image file not found: ${inputPath}`); } if (!stat.isFile()) { throw new Error(`Local image path is not a file: ${inputPath}`); } const fileBuffer = await fs.readFile(filePath); const fileName = path.basename(filePath); const mimeType = inferMimeType(filePath); const form = new FormData(); form.append('batch_no', makeUploadBatchNo()); form.append('fixed', 'false'); form.append('file', new Blob([fileBuffer], { type: mimeType }), fileName); const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 60_000); let res; try { res = await fetch(MODELS_BASE_URL + UPLOAD_API_PATH, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, }, body: form, signal: controller.signal, }); } catch (err) { clearTimeout(timer); if (err.name === 'AbortError') { throw new Error(`Upload timeout: ${filePath}`); } throw err; } clearTimeout(timer);js async function ensurePublicImageUrl(value, apiKey) { if (typeof value !== 'string' || !value.trim()) { throw new Error('Image source must be a non-empty string.'); } if (isPublicHttpsUrl(value)) return value; if (isRemote ...[truncated 3962 chars]- Remediation
View remediation
Remediation Suggestions
-
Disable local paths by default. Require
imageand everyimages[]entry to be a public HTTPS URL unless the operator explicitly enables local upload. -
Add an explicit capability flag. Require a command-line option such as
--allow-local-upload; reject local paths when the flag is absent. Do not infer consent merely from the presence of a path. -
Require an interactive or external confirmation step. Before reading a file, display its canonical path, size, detected type, and upload destination. Require affirmative confirmation unless a trusted non-interactive policy explicitly authorizes the operation.
-
Restrict access to an approved directory. Resolve both the approved root and requested path with
fs.realpath(), then verify that the requested file remains inside that root. Reject traversal outside it. -
Reject symbolic links. Use
fs.lstat()and fail if the selected path is a symbolic link. Revalidate the opened file to reduce time-of-check/time-of-use risks. -
Enforce image-only validation. Allow only documented image extensions and verify magic bytes for JPEG, PNG, WebP, and GIF before uploading. Do not fall back to
application/octet-stream. -
Enforce file-size and resource limits. Reject unexpectedly large files before
fs.readFile(), or stream uploads with a strict byte limit to avoid excessive memory consumption. -
Separate upload authorization where possible. Use a narrowly scoped credential for file uploads rather than a general-purpose generation credential, subject to WeryAI API support.
-
Align tests and documentation with runtime behavior. Add negative tests proving that files such as
.env, private keys, files outside the approved directory, symlinks, and files with forged image extensions are rejected. -
Prefer removing the feature if unnecessary. Public HTTPS image URLs already satisfy the declared image-to-video workflow and represent the minimum-priv ...[truncated 13 chars]
-
