T09 · Insecure Skill Coding Practices
- Location
lib/tts-core.js:78- Finding
Shell Command Injection in FFmpeg Invocation
- Content
View full analysis
Vulnerability Details
File Location:
lib/tts-core.js:78-87;scripts/voice-reply.js:34-36
Vulnerability Type: OS command injection through unvalidated configuration values and file paths
Risk Level: HighVulnerable Code
lib/tts-core.js:78-87:javascript if (outputPath) { const pcmPath = outputPath.replace(/\.(mp3|opus)$/, '.pcm'); fs.writeFileSync(pcmPath, pcmAudio); const { execSync } = require('child_process'); if (outputPath.endsWith('.mp3')) { execSync(`ffmpeg -y -f s16le -ar 16000 -ac 1 -i ${pcmPath} -ar ${this.audioConfig.outputSampleRate} -ac 1 ${outputPath} 2>/dev/null`); } else if (outputPath.endsWith('.opus')) { execSync(`ffmpeg -y -f s16le -ar 16000 -ac 1 -i ${pcmPath} -c:a libopus -b:a ${this.audioConfig.outputBitrate} -ar ${this.audioConfig.outputSampleRate} -ac 1 ${outputPath} 2>/dev/null`); }scripts/voice-reply.js:34-36:javascript execSync(`ffmpeg -y -i ${mp3Path} -c:a libopus -b:a ${config.xunfei.audio.outputBitrate} -ar ${config.xunfei.audio.outputSampleRate} -ac 1 ${opusPath} 2>/dev/null`, { stdio: 'pipe' });Technical Analysis
The code constructs shell command strings using template interpolation and passes them to
execSync. The interpolatedoutputBitrateandoutputSampleRatevalues originate from editableconfig.jsoncontent and are loaded without schema or type validation. The reusablesynthesizemethod also accepts an arbitraryoutputPath, which is inserted into a shell command without quoting.Because
execSyncexecutes the supplied string through a shell, shell metacharacters in any attacker-controlled value can terminate or alter the intended FFmpeg command. Quoting values alone would remain error-prone; the appropriate control is to avoid invoking a shell and pass each argument separately.The default configuration is benign, so exploitation requires the attacker to modify configuration, influence an invocation of the exported library, or compro ...[truncated 1224 chars]
- Remediation
View remediation
Remediation Suggestions
- Replace every string-based
execSynccall withexecFileSyncorspawnSyncusing an argument array and no shell:
javascript const { execFileSync } = require('child_process'); execFileSync('ffmpeg', [ '-y', '-f', 's16le', '-ar', '16000', '-ac', '1', '-i', pcmPath, '-c:a', 'libopus', '-b:a', validatedBitrate, '-ar', String(validatedSampleRate), '-ac', '1', outputPath ], { stdio: 'pipe' });- Require
outputSampleRateto be an integer selected from an explicit allowlist, such as16000,24000, or48000. - Validate
outputBitrateagainst a strict allowlist or a pattern such as^[1-9][0-9]*k$, followed by an acceptable numeric range check. - Resolve and normalize output paths, then verify that they remain inside a dedicated private output directory.
- Reject unexpected configuration keys and types using a JSON schema or equivalent runtime validator.
- Add tests containing spaces and shell metacharacters to confirm that values cannot affect command structure.
- Replace every string-based
