T09 · Insecure Skill Coding Practices
- Location
src/sync.ts:39- Finding
Shell Command Injection in the Exported Calendar Synchronization API
- Content
View full analysis
Vulnerability Details
File Location:
src/sync.ts, lines 39-42
Vulnerability Type: OS command injection through unsafe shell interpolation
Risk Level: HighVulnerable Code
typescript const { stdout, stderr } = await execAsync( `python3 "${scriptPath}" --market ${market}`, { timeout: 60000 } );Technical Analysis
CalendarSyncer.syncMarket()constructs a shell command by directly interpolatingmarketinto a string passed tochild_process.exec(). Theexec()API invokes a shell, so shell metacharacters contained in the value are interpreted as command syntax rather than as part of a literal argument.Although
markethas the compile-time TypeScript typeMarket, TypeScript types are erased at runtime. TheCalendarSyncerclass and singleton are publicly exported, andsyncMarket()does not independently validate its argument. The command-level handler validates market values before calling this method, but direct API consumers can bypass that handler.The constructor-controlled
scriptsPathis also interpolated into the shell command. Quoting the path is insufficient protection if an untrusted caller can instantiateCalendarSyncerwith a path containing quote characters or shell syntax.Attack Path
- An application exposes or otherwise invokes the exported
calendarSyncer.syncMarket()method using an attacker-controlled value. - The attacker supplies a runtime string containing shell metacharacters instead of one of the expected market identifiers.
syncMarket()inserts that value into the command string without runtime validation or shell escaping.execAsync()passes the assembled string to the operating-system shell.- The shell interprets the injected syntax and executes an additional attacker-selected command.
The normal
/finance-cron synchandler validates values and is not, by itself, a demonstrated injection entry point. Exploitation applies ...[truncated 704 chars]- An application exposes or otherwise invokes the exported
- Remediation
View remediation
Remediation Suggestions
-
Replace
exec()withexecFile()orspawn()and pass arguments as a separate array so no shell parses the values:typescript import { execFile } from 'child_process'; import { promisify } from 'util'; const execFileAsync = promisify(execFile); const allowedMarkets: readonly Market[] = ['US', 'CN', 'HK']; async syncMarket(market: Market): Promise<SyncResult> { if (!allowedMarkets.includes(market)) { throw new Error('Invalid market'); } const scriptPath = path.resolve(this.scriptsPath, 'sync_calendars.py'); const { stdout, stderr } = await execFileAsync( 'python3', [scriptPath, '--market', market], { timeout: 60000 } ); // Continue processing the result. } -
Perform runtime allowlist validation inside
syncMarket()itself, even if callers are also expected to validate input. -
Resolve the script path to a canonical absolute path and verify that it remains within the expected bundled scripts directory.
-
Avoid accepting a caller-controlled scripts directory unless this configurability is required. If required, validate it before use.
-
Add negative tests using shell metacharacters, quotes, command substitutions, and invalid market names to verify that they cannot alter process execution.
-
