Skip to content

Commit 5fe1486

Browse files
MathurAditya724autofix-ci[bot]ghostdevv
authored
fix(cli): use a shared cross-platform npm process resolver without shell (#2257)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Willow (GHOST) <git@willow.sh>
1 parent e88c6e4 commit 5fe1486

File tree

3 files changed

+33
-11
lines changed

3 files changed

+33
-11
lines changed

cli/src/cli.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ import { serve } from 'srvx'
88
import { createConnectorApp, generateToken, CONNECTOR_VERSION } from './server.ts'
99
import { getNpmUser, NPM_REGISTRY_URL } from './npm-client.ts'
1010
import { initLogger, showToken, logInfo, logWarning, logError } from './logger.ts'
11+
import { resolveNpmProcessCommand } from './npm-process.ts'
1112

1213
const DEFAULT_PORT = 31415
1314
const DEFAULT_FRONTEND_URL = 'https://npmx.dev/'
1415
const DEV_FRONTEND_URL = 'http://127.0.0.1:3000/'
1516

1617
async function runNpmLogin(): Promise<boolean> {
18+
const { command, args } = resolveNpmProcessCommand(['login', `--registry=${NPM_REGISTRY_URL}`])
19+
const child = spawn(command, args, { stdio: 'inherit' })
20+
1721
const { promise, resolve } = Promise.withResolvers<boolean>()
18-
const child = spawn('npm', ['login', `--registry=${NPM_REGISTRY_URL}`], {
19-
stdio: 'inherit',
20-
shell: true,
21-
})
2222

2323
child.on('close', code => {
2424
resolve(code === 0)

cli/src/npm-client.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { join } from 'node:path'
88
import * as v from 'valibot'
99
import { PackageNameSchema, UsernameSchema, OrgNameSchema, ScopeTeamSchema } from './schemas.ts'
1010
import { logCommand, logSuccess, logError, logDebug } from './logger.ts'
11+
import { resolveNpmProcessCommand } from './npm-process.ts'
1112

1213
const execFileAsync = promisify(execFile)
1314
export const NPM_REGISTRY_URL = 'https://registry.npmjs.org/'
@@ -333,13 +334,10 @@ async function execNpm(args: string[], options: ExecNpmOptions = {}): Promise<Np
333334

334335
try {
335336
logDebug('Executing npm command:', { command: 'npm', args: npmArgs })
336-
// Use execFile instead of exec to avoid shell injection vulnerabilities
337-
// On Windows, shell: true is required to execute .cmd files (like npm.cmd)
338-
// On Unix, we keep it false for better security and performance
339-
const { stdout, stderr } = await execFileAsync('npm', npmArgs, {
337+
const { command, args: processArgs } = resolveNpmProcessCommand(npmArgs)
338+
const { stdout, stderr } = await execFileAsync(command, processArgs, {
340339
timeout: 60000,
341340
env: createNpmEnv(),
342-
shell: process.platform === 'win32',
343341
})
344342

345343
logDebug('Command succeeded:', { stdout, stderr })
@@ -610,11 +608,11 @@ export async function packageInit(
610608
logCommand(`${displayCmd} (in temp dir for ${name})`)
611609

612610
try {
613-
const { stdout, stderr } = await execFileAsync('npm', npmArgs, {
611+
const { command, args: processArgs } = resolveNpmProcessCommand(npmArgs)
612+
const { stdout, stderr } = await execFileAsync(command, processArgs, {
614613
timeout: 60000,
615614
cwd: tempDir.path,
616615
env: createNpmEnv(),
617-
shell: process.platform === 'win32',
618616
})
619617

620618
logSuccess(`Published ${name}@0.0.0`)

cli/src/npm-process.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import process from 'node:process'
2+
3+
interface NpmProcessCommand {
4+
command: string
5+
args: string[]
6+
}
7+
8+
export function resolveNpmProcessCommand(
9+
npmArgs: string[],
10+
platform = process.platform,
11+
comSpec = process.env.ComSpec,
12+
): NpmProcessCommand {
13+
if (platform === 'win32') {
14+
return {
15+
command: comSpec || 'cmd.exe',
16+
args: ['/d', '/s', '/c', 'npm', ...npmArgs],
17+
}
18+
}
19+
20+
return {
21+
command: 'npm',
22+
args: npmArgs,
23+
}
24+
}

0 commit comments

Comments
 (0)