T09 · Insecure Skill Coding Practices
- Location
src/commands/init.js:20- Finding
OS Command Injection Through the Project Name
- Content
View full analysis
Vulnerability Details
File Location:
src/commands/init.js:20-83
Vulnerability Type: OS command injection through shell interpolation
Risk Level: HighVulnerable Code
javascript // Validate project name if (!projectName || projectName.length === 0) { spinner.fail('Project name is required'); process.exit(1); } // Create project with create-next-app const createNextAppCommand = [ 'npx', 'create-next-app@latest', projectName, '--typescript', '--tailwind', '--eslint', '--app', '--src-dir', '--import-alias "@/*"', '--use-npm' ].filter(Boolean); execSync(createNextAppCommand.join(' '), { stdio: 'inherit' }); const projectPath = path.join(process.cwd(), projectName); // Install Supabase dependencies const installCommand = `cd ${projectPath} && npm install @supabase/supabase-js @supabase/ssr`; execSync(installCommand, { stdio: 'inherit' }); // Initialize git execSync(`cd ${projectPath} && git init`, { stdio: 'ignore' });Technical Analysis
The positional
projectNameargument is controlled by the CLI caller. Validation only checks whether the value is empty; it does not restrict shell metacharacters or enforce a valid npm package-name format.The value is subsequently:
- Joined into a shell command passed to
execSync(). - Included in
projectPath. - Interpolated into two additional shell commands.
By default,
execSync()executes command strings through a system shell. Characters such as command separators, redirection operators, and command substitutions can therefore alter the intended command. The vulnerability is not limited to creation of an unexpected directory: injected shell commands execute with the privileges of the user runningsnv.Attack Path
- An attacker convinces a user or automation system to initialize a project using a crafted project-name argument.
- The CLI verifies on ...[truncated 855 chars]
- Joined into a shell command passed to
- Remediation
View remediation
Remediation Suggestions
- Replace shell command strings with
spawnSync()orexecFileSync()and explicit argument arrays. - Ensure shell interpretation is disabled.
- Validate project names against a strict npm/Next.js-compatible allowlist, rejecting whitespace, path separators, shell metacharacters, and traversal sequences.
- Use the
cwdprocess option rather than constructingcd path && commandstrings. - Verify that the resolved project path remains inside the intended parent directory.
A safer implementation pattern is:
javascript const { spawnSync } = require('child_process'); if (!/^[a-z0-9][a-z0-9._-]*$/.test(projectName)) { throw new Error('Invalid project name'); } spawnSync( 'npx', [ 'create-next-app@reviewed-version', projectName, '--typescript', '--tailwind', '--eslint', '--app', '--src-dir', '--import-alias', '@/*', '--use-npm' ], { stdio: 'inherit', shell: false } ); spawnSync( 'npm', ['install', '@supabase/supabase-js', '@supabase/ssr'], { cwd: projectPath, stdio: 'inherit', shell: false } ); spawnSync('git', ['init'], { cwd: projectPath, stdio: 'ignore', shell: false });- Replace shell command strings with
