VYPR
Critical severityNVD Advisory· Published Dec 19, 2023· Updated Aug 5, 2024

pedroetb tts-api app.js onSpeechDone os command injection

CVE-2019-25158

Description

Critical OS command injection in pedroetb/tts-api up to 2.1.4 allows remote code execution; upgrade to v2.2.0 to fix.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Critical OS command injection in pedroetb/tts-api up to 2.1.4 allows remote code execution; upgrade to v2.2.0 to fix.

Vulnerability

CVE-2019-25158 is a critical OS command injection vulnerability in the onSpeechDone function of app.js in pedroetb/tts-api versions up to and including 2.1.4. The function constructs shell commands by concatenating user-supplied parameters (such as text, language, and speed) without proper sanitization or escaping, allowing an attacker to inject arbitrary OS commands [1][2].

Exploitation

The vulnerability is exploitable by sending a crafted HTTP POST request to the TTS API endpoint. The attacker does not require authentication, as the API is typically exposed without access controls. By manipulating the textToSpeech or other fields, the attacker can break out of the intended command and execute arbitrary system commands on the server [2][3].

Impact

Successful exploitation leads to remote code execution with the privileges of the tts-api process, which often runs as a non-root user but can still compromise the host system, exfiltrate data, or launch further attacks [1][3].

Mitigation

The vendor fixed the issue in version 2.2.0 by rewriting the command construction to use parameterized arrays instead of string concatenation, effectively preventing injection [2]. Users should upgrade immediately. Alternatively, the patch can be applied manually (commit 29d9c25415911ea2f8b6de247cb5c4607d13d434) [2][4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
tts-apinpm
< 2.2.02.2.0

Affected products

2

Patches

1
29d9c2541591

Fix command injection vulnerability and other bugs

https://github.com/pedroetb/tts-apiPedro TrujilloJul 14, 2019via ghsa
1 file changed · +186 38
  • app.js+186 38 modified
    @@ -20,8 +20,7 @@ server.set('view engine', 'pug')
     
     	.listen(port, function() {
     
    -		var port = this.address().port;
    -		console.log('Listening at port', port);
    +		console.log('Listening at port', this.address().port);
     	});
     
     function renderForm(req, res) {
    @@ -33,17 +32,16 @@ function renderForm(req, res) {
     function processData(req, res) {
     
     	var body = req.body,
    -		exec = childProcess.exec,
    -		cmd = getCmdWithArgs(body),
    -		cbk = onSpeechDone.bind(this, {
    +		cmdWithArgs = getCmdWithArgs(body) || {},
    +		httpArgs = {
     			res: res,
     			fields: body
    -		});
    +		};
     
    -	if (!cmd) {
    -		cbk('Empty command generated');
    +	if (cmdWithArgs instanceof Array) {
    +		runSpeechProcessChain(cmdWithArgs, httpArgs);
     	} else {
    -		exec(cmd, cbk);
    +		runLastSpeechProcess(cmdWithArgs, httpArgs);
     	}
     }
     
    @@ -60,71 +58,221 @@ function getCmdWithArgs(fields) {
     	} else if (voice === 'espeak') {
     		return getEspeakCmdWithArgs(fields);
     	}
    -
    -	return '';
     }
     
     function getGoogleSpeechCmdWithArgs(fields) {
     
     	var text = fields.textToSpeech,
     		language = fields.language,
    -		speed = fields.speed,
    -
    -		cmd = 'google_speech' + ' -l ' + language + ' \"' + text + '\"' + ' -e overdrive 10 speed ' + speed;
    -
    -	return cmd;
    +		speed = fields.speed;
    +
    +	return {
    +		cmd: 'google_speech',
    +		args: [
    +			'-v', 'warning',
    +			'-l', language,
    +			text,
    +			'-e',
    +			'gain', '4',
    +			'speed', speed
    +		]
    +	};
     }
     
     function getGttsCmdWithArgs(fields) {
     
     	var text = fields.textToSpeech,
     		language = fields.language,
     		speed = fields.speed,
    -		speedParam = speed ? ' -s' : '',
    -
    -		cmd = 'gtts-cli' + ' -l ' + language + speedParam + ' --nocheck \"' + text + '\"' + ' | play -t mp3 -';
    -
    -	return cmd;
    +		slowSpeed = fields.slowSpeed ? '-s' : '';
    +
    +	return [{
    +		cmd: 'gtts-cli',
    +		args: [
    +			'-l', language,
    +			'--nocheck',
    +			slowSpeed,
    +			text
    +		]
    +	},{
    +		cmd: 'play',
    +		args: [
    +			'-q',
    +			'-t', 'mp3',
    +			'-',
    +			'gain', '4',
    +			'speed', speed
    +		]
    +	}];
     }
     
     function getFestivalCmdWithArgs(fields) {
     
     	var text = fields.textToSpeech,
    -		language = fields.language,
    -
    -		cmd = 'echo "' + text + '" | festival' + ' --tts --heap 1000000 --language ' + language;
    -
    -	return cmd;
    +		language = fields.language;
    +
    +	return [{
    +		cmd: 'echo',
    +		args: [
    +			text
    +		]
    +	},{
    +		cmd: 'festival',
    +		args: [
    +			'--tts',
    +			'--language', language,
    +			'--heap', '1000000'
    +		]
    +	}];
     }
     
     function getEspeakCmdWithArgs(fields) {
     
     	var text = fields.textToSpeech,
     		language = fields.language,
     		voiceCode = '+f4',
    +		voice = language + voiceCode,
     		speed = Math.floor(fields.speed * 150),
    -		pitch = '70',
    +		pitch = '70';
    +
    +	return {
    +		cmd: 'espeak',
    +		args: [
    +			'-v', voice,
    +			'-s', speed,
    +			'-p', pitch,
    +			text
    +		]
    +	};
    +}
    +
    +function runLastSpeechProcess(cmdWithArgs, httpArgs) {
    +
    +	var speechProcess = runSpeechProcess(cmdWithArgs);
     
    -		cmd = 'espeak' + ' -v' + language + voiceCode + ' -s ' + speed + ' -p ' + pitch + ' \"' + text + '\"';
    +	speechProcess.on('error', onLastSpeechError.bind(this, httpArgs));
    +	speechProcess.on('close', onLastSpeechClose);
    +	speechProcess.on('exit', onLastSpeechExit.bind(this, httpArgs));
     
    -	return cmd;
    +	return speechProcess;
     }
     
    -function onSpeechDone(args, err, stdout, stderr) {
    +function runSpeechProcess(cmdWithArgs) {
     
    -	var res = args.res,
    -		fields = args.fields;
    +	var newProcess = childProcess.spawn(cmdWithArgs.cmd, cmdWithArgs.args);
    +
    +	newProcess.stderr.on('data', onSpeechStandardError);
    +
    +	return newProcess;
    +}
    +
    +function onSpeechStandardError(buffer) {
    +
    +	console.error('[stderr]:', buffer.toString('utf8'));
    +}
    +
    +function runSpeechProcessChain(cmdWithArgs, httpArgs) {
    +
    +	var speechProcs = {};
    +
    +	for (var i = 0; i < cmdWithArgs.length; i++) {
    +		if (i !== cmdWithArgs.length - 1) {
    +			var getNextProcessCbk = getNextSpeechProcess.bind(speechProcs, i + 1);
    +			speechProcs[i] = runIntermediateSpeechProcess(cmdWithArgs[i], getNextProcessCbk);
    +		} else {
    +			speechProcs[i] = runLastSpeechProcess(cmdWithArgs[i], httpArgs);
    +		}
    +	}
    +}
    +
    +function runIntermediateSpeechProcess(cmdWithArgs, procArgs) {
    +
    +	var speechProcess = runSpeechProcess(cmdWithArgs);
    +
    +	speechProcess.stdout.on('data', onIntermediateSpeechStandardOutput.bind(this, procArgs));
    +	speechProcess.on('error', onIntermediateSpeechError);
    +	speechProcess.on('close', onIntermediateSpeechClose.bind(this, procArgs));
    +
    +	return speechProcess;
    +}
    +
    +function getNextSpeechProcess(nextIndex) {
    +
    +	return this[nextIndex];
    +}
    +
    +function onIntermediateSpeechStandardOutput(getNextProc, data) {
    +
    +	var nextSpeechProcess = getNextProc(),
    +		inputStream = nextSpeechProcess.stdin;
    +
    +	if (inputStream.writable) {
    +		inputStream.write(data);
    +	}
    +}
    +
    +function onIntermediateSpeechClose(getNextProc, code) {
    +
    +	var nextSpeechProcess = getNextProc(),
    +		inputStream = nextSpeechProcess.stdin;
    +
    +	if (code) {
    +		console.error('[intermediate exit code]:', code);
    +	}
    +
    +	inputStream.end();
    +}
    +
    +function onIntermediateSpeechError(err) {
    +
    +	console.error('[intermediate error]:', util.inspect(err));
    +}
    +
    +function onLastSpeechClose(code) {
    +
    +	if (code) {
    +		console.error('[exit code]:', code);
    +	}
    +}
    +
    +function onLastSpeechExit(args, err) {
    +
    +	var res = args.res;
     
     	if (!err) {
     		res.end();
    -		return;
    +	} else {
    +		handleSpeechError(args, err);
     	}
    +}
    +
    +function onLastSpeechError(args, err) {
    +
    +	handleSpeechError(args, err);
    +}
    +
    +function handleSpeechError(args, err) {
    +
    +	var res = args.res,
    +		fields = args.fields,
    +		errorHeaderMessage = '----[error]----',
    +		dataHeaderMessage = '-----[data]-----',
    +		inspectedError = util.inspect(err),
    +		inspectedFields = util.inspect(fields);
     
     	res.writeHead(500, {
    -		'content-type': 'text/plain'
    +		'Content-Type': 'text/plain; charset=utf-8'
     	});
    -	res.write('error:\n\n');
    -	res.write(util.inspect(err) + '\n\n');
    -	res.write('received data:\n\n');
    -	res.end(util.inspect(fields));
    +
    +	res.write(errorHeaderMessage + '\n');
    +	res.write(inspectedError + '\n');
    +	res.write(dataHeaderMessage + '\n');
    +	res.write(inspectedFields + '\n');
    +
    +	res.end();
    +
    +	console.error(errorHeaderMessage);
    +	console.error(inspectedError);
    +	console.error(dataHeaderMessage);
    +	console.error(inspectedFields);
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.