T09 · Insecure Skill Coding Practices
- Location
scripts/generate.js:321- Finding
Arbitrary Local File Disclosure Through Unrestricted Input Image Path
- Content
View full analysis
Vulnerability Details
File Location:
scripts/generate.js:321-325, with the network transmission sink inscripts/adapters/openrouter.js:62-65andscripts/adapters/openrouter.js:262-270
Vulnerability Type: Unrestricted local file read and external transmission
Risk Level: MediumVulnerable Code
scripts/generate.js:321-325reads the user-supplied path without validating its location, type, size, or resolved target:javascript if (inputImagePath) { try { const imageBuffer = fs.readFileSync(inputImagePath); inputImageBase64 = imageBuffer.toString('base64');scripts/generate.js:515-522passes the CLI-controlled--input-imagevalue into that operation:javascript const result = await generateImageWithRetry( apiKey, argMap['i2i-model'], argMap.model, argMap.prompt, argMap.size, argMap.aspect, argMap['input-image'] );scripts/adapters/openrouter.js:62-65embeds the resulting bytes in an outbound request while unconditionally labeling them as PNG:javascript content: inputImageBase64 ? [ { type: 'text', text: prompt }, { type: 'image_url', image_url: { url: `data:image/png;base64,${inputImageBase64}` } } ] : promptscripts/adapters/openrouter.js:262-270transmits the payload to OpenRouter:javascript const response = await fetch(OPENROUTER_API_URL, { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', 'HTTP-Referer': 'https://github.com/ohmyopencodelabs/ai-workers', 'X-Title': 'AI Workers Image Generation' }, body: JSON.stringify(payload) });Technical Analysis
The
--input-imageargument is treated as a trusted local path. The implementation callsfs.readFileSync()directly and does not verify that:- The resolved path is inside an approved workspace ...[truncated 2032 chars]
- Remediation
View remediation
Remediation Suggestions
-
Restrict input files to an approved root
- Resolve the configured upload or workspace root with
fs.realpathSync(). - Resolve the requested file in the same manner.
- Use
path.relative()to verify that the final target remains beneath the approved root. - Reject absolute paths unless explicitly permitted by policy.
- Resolve the configured upload or workspace root with
-
Reject symlinks and non-regular files
- Use
fs.lstatSync()before reading. - Reject symbolic links, device files, FIFOs, sockets, and directories.
- Revalidate the resolved file immediately before opening it to reduce time-of-check/time-of-use risk.
- Use
-
Validate the actual image format
- Inspect file signatures rather than trusting the extension.
- Allowlist only required formats, such as PNG, JPEG, and WebP.
- Set the transmitted data URL MIME type from the verified format instead of always using
image/png.
-
Enforce resource limits
- Check file size before reading and reject files above a conservative configured maximum.
- Prefer bounded asynchronous reads over unbounded synchronous loading.
- Apply request-size and timeout limits.
-
Require informed authorization
- Clearly document that prompts and source images are transmitted to OpenRouter.
- Require explicit user approval before uploading a local file, especially when an agent selected the path.
- Include the normalized path and destination in the confirmation prompt.
-
Apply least-privilege runtime controls
- Run the Skill with access only to the required workspace and upload directories.
- Avoid exposing unrelated credential directories or host files to the process.
-
