T09 · Insecure Skill Coding Practices
- Location
scripts/generate_pet.js:222- Finding
OS Command Injection Through an Attacker-Controlled Output Path
- Content
View full analysis
Vulnerability Details
File Location:
scripts/generate_pet.js, lines 7, 222, 228, and 233
Vulnerability Type: OS command injection caused by unsafe shell command construction
Risk Level: HighVulnerable Code
The output path is obtained directly from command-line arguments:
js const args = process.argv.slice(2); const petType = args[0] || 'dog'; const outputPath = args[1] || '/tmp/pet.png';It is then used to derive another path and both values are interpolated into shell command strings:
js const svg = svgGenerator(colors, size); const svgPath = outputPath.replace('.png', '.svg'); // Write SVG fs.writeFileSync(svgPath, svg); // Convert to PNG try { // Try rsvg-convert first execSync(`rsvg-convert "${svgPath}" -o "${outputPath}"`, { stdio: 'inherit' }); console.log(`PNG created: ${outputPath}`); } catch (e) { // Fall back to ImageMagick try { execSync(`convert "${svgPath}" "${outputPath}"`, { stdio: 'inherit' }); console.log(`PNG created: ${outputPath}`); } catch (e2) {Technical Analysis
outputPathis attacker-controlled command-line input. The application places it inside a JavaScript template literal and passes the resulting string toexecSync. By default,execSyncexecutes string commands through a system shell.Wrapping the path in double quotes is not sufficient shell escaping. An attacker can supply embedded quotation marks to terminate the quoted argument and append shell operators and commands. Shell command substitutions may also be evaluated within double-quoted strings. The derived
svgPathremains unsafe because it is generated from the same untrusted input using only a string replacement.Both conversion attempts are independently vulnerable:
- The primary
rsvg-convertcommand interpolatessvgPathandoutputPath. - The ImageMagick fallback interpolates the same untrusted values and provides a second c ...[truncated 2012 chars]
- The primary
- Remediation
View remediation
Remediation Suggestions
-
Replace shell-based
execSynccalls withexecFileSyncorspawnSync, passing every argument through an argument array:js const { execFileSync } = require('child_process'); try { execFileSync('rsvg-convert', [svgPath, '-o', outputPath], { stdio: 'inherit' }); } catch (error) { execFileSync('convert', [svgPath, outputPath], { stdio: 'inherit' }); }This avoids shell parsing and ensures that filename characters are passed to the converter as data rather than executable shell syntax.
-
Validate and normalize the output path before use:
- Require the output filename to have a
.pngextension. - Reject null bytes and control characters.
- Resolve the path with
path.resolve. - If output must remain in a designated directory, verify that the resolved path is contained within that directory.
- Consider generating the filename internally rather than accepting an arbitrary path.
- Require the output filename to have a
-
Derive the SVG path using the
pathmodule instead of unrestricted string replacement:js const path = require('path'); const parsed = path.parse(outputPath); const svgPath = path.join(parsed.dir, `${parsed.name}.svg`); -
Run conversion utilities under a minimally privileged account with restricted filesystem and network access.
-
Add regression tests using paths containing quotes, semicolons, command-substitution characters, whitespace, and traversal sequences. Tests should verify that these values are either rejected or passed as literal process arguments without shell interpretation.
-
Avoid attempting to repair this issue with ad hoc shell escaping. A shell-free process invocation API is the preferred and more reliable control.
-
