T09 · Insecure Skill Coding Practices
- Location
tests/run-tests.js:56- Finding
Shell Command Injection Through Unvalidated Test Runner Arguments
- Content
View full analysis
Vulnerability Details
File Location:
tests/run-tests.js, lines 56-69
Vulnerability Type: OS command injection
Risk Level: HighVulnerable Code:
javascript // Add any remaining args const remainingArgs = args.filter( arg => !['--unit', '--integration', '--e2e', '--coverage', '--verbose', '-v', '--watch', '-w'].includes(arg) ); jestArgs.push(...remainingArgs); console.log('Running tests...\n'); console.log(`Command: ${command} ${jestArgs.join(' ')}\n`); try { execSync(`${command} ${jestArgs.join(' ')}`, { stdio: 'inherit', cwd: __dirname });Technical Analysis
Unrecognized command-line arguments are appended directly to
jestArgs. The resulting array is joined into a string and passed toexecSync, which invokes the operating system shell by default.Because the arguments are neither escaped nor constrained to an allowlist, shell metacharacters such as command substitution, semicolons, pipes, and redirection operators retain their shell meaning. An argument intended for Jest can therefore alter the command executed by the shell.
Although this file is test tooling rather than the runtime Skill implementation, executing the provided test runner with attacker-controlled arguments can result in arbitrary local command execution.
Attack Path
- An attacker supplies or persuades a user, CI task, or wrapper script to pass a crafted argument to
run-tests.js. - The crafted value is not recognized by the option filter, so it is added to
remainingArgs. jestArgs.join(' ')incorporates the value into a shell command without escaping.execSyncpasses the constructed string to the system shell.- The shell interprets injected metacharacters and executes the attacker's additional command.
- The injected command runs with the same operating-system identity and privileges as the user or CI worker running the tests.
Impact Assessment
S ...[truncated 645 chars]
- An attacker supplies or persuades a user, CI task, or wrapper script to pass a crafted argument to
- Remediation
View remediation
Remediation Suggestions
-
Do not construct a shell command by joining arguments into a string.
-
Invoke Node directly with
execFileSyncorspawnSyncand pass each argument as a distinct array element:javascript import { execFileSync } from 'child_process'; execFileSync(process.execPath, jestArgs, { stdio: 'inherit', cwd: __dirname, shell: false }); -
Reject unknown arguments rather than forwarding every unrecognized value.
-
If additional Jest options must be supported, define an explicit allowlist and validate each option's expected value.
-
Avoid relying on manual shell escaping, because escaping requirements vary across operating systems and shells.
-
Add regression tests containing semicolons, pipes, command substitution, redirection characters, spaces, and quotes to verify that they cannot trigger shell interpretation.
-
