T09 · Insecure Skill Coding Practices
Error
- Location
- lib/scanner.js:212
- Finding
- Shell Command Injection Through Code-Signature Verification<![CDATA[ ## Vulnerability Details **File Location**: `lib/scanner.js:212-227` **Vulnerability Type**: OS command injection through shell interpolation **Risk Level**: High ### Complete Vulnerable Code ```javascript function checkCodeSignature(filePath) { try { execSync(`codesign --verify --deep --strict "${filePath}" 2>&1`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }); return { valid: true, signed: true }; } catch (err) { const output = err.stdout || err.stderr || ''; if (output.includes('not signed')) { return { valid: false, signed: false, error: 'Not signed' }; } return { valid: false, signed: true, error: output.trim() }; } } ``` The attacker-controlled path originates from the CLI and reaches the vulnerable function through the following logic: ```javascript const targetPath = args.find(a => !a.startsWith('--')); const fullPath = path.resolve(targetPath); results = [await scanFile(fullPath, options)]; ``` ### Technical Analysis `checkCodeSignature()` inserts `filePath` directly into a command string passed to `child_process.execSync()`. Unlike argument-based process execution, `execSync()` runs the string through a shell. Surrounding the path with double quotes does not make this safe. Shell command substitution such as `$(command)` remains active inside double quotes. A filename containing a double quote can also terminate the quoted argument and introduce shell control operators. The vulnerable branch is reached on macOS when the scanned file is a Mach-O binary or has its owner-executable permission bit set: ```javascript if (process.platform === 'darwin' && (result.type === 'macho' || isExecutable(filePath))) { result.details.codesign = checkCodeSignature(filePath); } ``` The scanner is explicitly intended to process untrusted files, so filenames and paths must be treated as hostile input. ### Attack Path 1. An attacker creates or distributes a Mach-O or executable file whose ...[truncated 1360 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Avoid invoking a shell for code-signature verification. Use an argument-array API so that the path is passed literally: ```javascript const { execFileSync } = require('child_process'); function checkCodeSignature(filePath) { try { execFileSync( 'codesign', ['--verify', '--deep', '--strict', filePath], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 30000 } ); return { valid: true, signed: true }; } catch (err) { const output = String(err.stderr || err.stdout || ''); if (output.includes('not signed')) { return { valid: false, signed: false, error: 'Not signed' }; } return { valid: false, signed: true, error: output.trim() }; } } ``` Additional hardening should include: 1. Do not set `shell: true`. 2. Add a timeout to prevent a stalled external process from blocking a scan indefinitely. 3. Test filenames containing spaces, quotes, dollar signs, backticks, semicolons, newlines, and command-substitution syntax. 4. Run the scanner as an unprivileged account and document that users should not invoke it through `sudo`. 5. Consider constraining the child process environment and resolving the trusted `codesign` executable path explicitly where appropriate. ]]>
