T09 · Insecure Skill Coding Practices
- Location
scripts/batch_screen.js:40- Finding
Shell Command Injection in Batch Resume Screening
- Content
View full analysis
Vulnerability Details
File Location:
scripts/batch_screen.js, lines 40-49
Vulnerability Type: OS command injection through shell command construction
Risk Level: CriticalVulnerable Code
js // Handle PDF conversion if (file.toLowerCase().endsWith('.pdf')) { const tempTxt = path.join('/tmp', file + '.txt'); execSync('pdftotext "' + filePath + '" "' + tempTxt + '"'); processPath = tempTxt; isTemp = true; } const output = execSync('node ' + path.join(__dirname, 'screen_resume.js') + ' "' + processPath + '" --lang "' + lang + '" --yoe "' + yoe + '"', { encoding: 'utf8' });Technical Analysis
The script concatenates several values into command strings passed to
child_process.execSync, which invokes a system shell:filePathandtempTxtare derived from filenames found in a user-selected directory.langcomes from the user-controlled--langargument.yoecomes from the user-controlled--yoeargument.
Wrapping these values in double quotes does not make the operation safe. A value containing a quote, command substitution, or another shell-significant construct can terminate or alter the intended argument and introduce additional shell operations.
This is particularly dangerous for filenames because the batch workflow automatically enumerates files with accepted extensions. An attacker able to place a specially named PDF, text file, or Markdown file in the processed directory may reach the vulnerable command without controlling the command-line flags directly.
Attack Path
- The attacker supplies a resume with a shell-significant filename, or influences the
--langor--yoeoption. - The operator runs
batch_screen.jsagainst the affected directory or with the affected option. - The script concatenates the untrusted value into an
execSynccommand string. - The system shell interprets the injected syntax rather than treating the entire value as a literal argument.
- The injected command e ...[truncated 749 chars]
- Remediation
View remediation
Remediation Suggestions
- Replace
execSyncwithexecFileSyncorspawnSync, passing each argument as a separate array element so no shell interprets user input. - Invoke the current Node executable through
process.execPathrather than constructing anodeshell command. - Validate
--langand--yoeagainst strict expected formats or allowlists. - Reject filenames containing control characters and enforce canonical path containment within the selected source directory.
- Create temporary files through
fs.mkdtempSyncin a private temporary directory, using server-generated names instead of source filenames. - Ensure temporary files are cleaned in a
finallyblock. - Run document conversion in a sandbox with minimal filesystem and network permissions.
A safer invocation pattern is:
js const { execFileSync } = require('child_process'); execFileSync('pdftotext', [filePath, tempTxt], { stdio: ['ignore', 'pipe', 'pipe'], shell: false }); const output = execFileSync( process.execPath, [path.join(__dirname, 'screen_resume.js'), processPath, '--lang', lang, '--yoe', yoe], { encoding: 'utf8', shell: false } );- Replace
