T09 · Insecure Skill Coding Practices
- Location
scripts/render.mjs:159- Finding
Command Injection Through the Audio File Path
- Content
View full analysis
Vulnerability Details
File Location:
scripts/render.mjs:159-162
Vulnerability Type: OS command injection
Risk Level: HighVulnerable Code
js function getAudioDuration(filePath) { try { const result = execSync( `ffprobe -v error -show_entries format=duration -of csv=p=0 "${filePath}"`, {encoding: 'utf-8'} ).trim(); return parseFloat(result); } catch { return null; } }Technical Analysis
The
filePathvalue is derived from the user-controlled--audioargument and inserted directly into a shell command passed toexecSync(). Although the value is enclosed in double quotes, embedded quotes, command substitutions, and shell metacharacters are not escaped.Node.js executes the string through a command shell. Consequently, a malicious audio filename can terminate the quoted argument and append another shell command. The wrapper verifies that the supplied audio file exists, but this does not prevent exploitation because filenames on supported operating systems can contain quotes and shell metacharacters.
Attack Path
- An attacker creates or supplies an accessible audio file whose path contains shell syntax.
- The attacker passes that path through the documented
--audiooption. resolveFilePath()resolves the path without removing shell metacharacters.- The audio is copied into
public/, preserving its basename. getAudioDuration()embeds the resulting path in theffprobecommand string.execSync()invokes a shell, which interprets the injected syntax and executes the appended command.
A conceptual malicious filename could contain syntax equivalent to:
text song.mp3"; attacker-command; #Impact Assessment
Successful exploitation provides arbitrary command execution with the privileges of the user or service running the Skill. The attacker could read or modify files accessible to that accoun ...[truncated 293 chars]
- Remediation
View remediation
Remediation Suggestions
Replace shell-string execution with an API that passes each argument directly to the executable:
js import {execFileSync} from 'child_process'; function getAudioDuration(filePath) { try { const result = execFileSync( 'ffprobe', [ '-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', filePath, ], { encoding: 'utf-8', shell: false, } ).trim(); const duration = Number.parseFloat(result); return Number.isFinite(duration) && duration > 0 ? duration : null; } catch { return null; } }Additional hardening should include:
- Resolve
ffprobeto a trusted executable or use a controlled executable search path. - Verify that the input is a regular file before processing it.
- Apply file size and media-duration limits to reduce denial-of-service exposure.
- Do not attempt to make shell interpolation safe through manual escaping; avoid invoking a shell entirely.
- Resolve
