T09 · Insecure Skill Coding Practices
- Location
pwsh-encoding-fix.js:121- Finding
OS Command Injection via Crafted JavaScript File Path
- Content
View full analysis
Vulnerability Details
File Location:
pwsh-encoding-fix.js, lines 121–123
Vulnerability Type: OS command injection
Risk Level: HighVulnerable Code
js if (filePath.endsWith('.js')) { execSync('node --check "' + absPath + '"', { stdio: 'pipe' }); }Technical Analysis
The script obtains
filePathfrom a command-line argument and derivesabsPathusingpath.resolve(). It then concatenates that attacker-influenced path into a command string passed toexecSync().By default,
execSync()executes the string through a system shell. WrappingabsPathin double quotes does not safely escape embedded quotation marks or shell metacharacters. A specially crafted filename can terminate the quoted path and append an additional command. The requirement that the input end in.jsdoes not prevent such a payload because the filename can retain that suffix after the injected shell expression.Path resolution canonicalizes the path but does not provide shell escaping or argument separation.
Attack Path
-
An attacker creates or supplies a JavaScript file whose filename contains shell syntax appropriate for the target operating system, including an embedded double quote that escapes the intended quoted argument.
-
The attacker persuades a user or agent to diagnose that file:
text node pwsh-encoding-fix.js <crafted-filename> -
main()passes the filename todiagnose(). -
diagnose()resolves the filename toabsPathand confirms that the original argument ends in.js. -
The script constructs a shell command by concatenating
absPathintonode --check "...". -
execSync()invokes the system shell, which interprets the injected quotation mark and metacharacters. -
The attacker's appended command executes with the privileges of the user running the skill.
Successful exploitation requires the crafted filename to be valid on the host filesystem and its shell syntax to match the operating syst ...[truncated 741 chars]
-
- Remediation
View remediation
Remediation Suggestions
Remove shell interpretation and pass the executable and its arguments separately. Use the current Node.js executable rather than relying on shell command lookup:
js const { execFileSync } = require('child_process'); if (filePath.endsWith('.js')) { execFileSync(process.execPath, ['--check', absPath], { stdio: 'pipe', shell: false }); }Additional hardening measures:
- Do not attempt to solve the issue solely through manual shell escaping; argument-based process APIs are safer and portable.
- Consider validating that the target is a regular file with
fs.statSync(absPath).isFile()before invoking Node.js. - If only expected file types should be processed, validate the extension using the resolved path and a strict allowlist.
- Add regression tests containing spaces, quotation marks, ampersands, semicolons, command-substitution characters, and platform-specific shell metacharacters in filenames.
- Preserve the existing captured-output behavior, but sanitize exception text before exposing it in contexts where terminal escape sequences or untrusted logs may be relevant.
