diff --git a/scripts/check-security.mjs b/scripts/check-security.mjs index 56be6acfa7..7adaef0cfe 100644 --- a/scripts/check-security.mjs +++ b/scripts/check-security.mjs @@ -254,8 +254,9 @@ async function checkWithMeterian(dependencies) { })); // Run Meterian CLI check using the found path + // Use process.execPath to avoid PATH-based attacks const cliScript = join(cliPath, 'src', 'cli.js'); - const result = spawnSync('node', [cliScript, 'check'], { + const result = spawnSync(process.execPath, [cliScript, 'check'], { input: JSON.stringify(input), encoding: 'utf-8', timeout: 60000, // 60 second timeout @@ -490,7 +491,7 @@ function matchesVersionRange(version, range) { const conditions = range.split(',').map(c => c.trim()); for (const condition of conditions) { - const match = condition.match(/^(>=|<=|>|<|=)?\s*(.+)$/); + const match = condition.match(/^(>=|<=|>|<|=)?\s*(\S+)$/); if (!match) continue; const [, operator = '=', targetVersion] = match; diff --git a/scripts/postinstall-security.mjs b/scripts/postinstall-security.mjs index 05f0d2bf92..ccbecd5bb9 100644 --- a/scripts/postinstall-security.mjs +++ b/scripts/postinstall-security.mjs @@ -38,6 +38,13 @@ import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT_DIR = join(__dirname, '..'); +// Use npm_execpath from environment (set by npm during lifecycle scripts) +// Falls back to 'npm' if not available (e.g., running script directly) +const NPM_PATH = process.env.npm_execpath || 'npm'; +const NPX_CMD = NPM_PATH.endsWith('npm-cli.js') + ? `"${process.execPath}" "${NPM_PATH.replace('npm-cli.js', 'npx-cli.js')}"` + : 'npx'; + // Packages that are trusted to run postinstall/install scripts // These typically need to compile native bindings or setup tooling const TRUSTED_PACKAGES = [ @@ -122,8 +129,8 @@ async function main() { console.log('Step 1/3: Running security check...\n'); const securityCheckPath = join(__dirname, 'check-security.mjs'); - // Run security check as subprocess (it calls process.exit) - const securityPassed = run(`node "${securityCheckPath}"`); + // Run security check as subprocess using process.execPath to avoid PATH-based attacks + const securityPassed = run(`"${process.execPath}" "${securityCheckPath}"`); if (!securityPassed) { console.error('\nāŒ Security check failed - installation aborted\n'); process.exit(1); @@ -138,7 +145,8 @@ async function main() { trustedInstalled.forEach(pkg => console.log(` āœ“ ${pkg}`)); console.log(''); - run(`npm rebuild --ignore-scripts=false ${trustedInstalled.join(' ')}`); + const npmCmd = NPM_PATH === 'npm' ? 'npm' : `"${process.execPath}" "${NPM_PATH}"`; + run(`${npmCmd} rebuild --ignore-scripts=false ${trustedInstalled.join(' ')}`); } else { console.log('No trusted packages require rebuilding.\n'); } @@ -147,7 +155,7 @@ async function main() { console.log('Step 3/3: Setting up husky...\n'); const huskyPath = join(ROOT_DIR, 'node_modules', 'husky'); if (existsSync(huskyPath)) { - run('npx husky'); + run(`${NPX_CMD} husky`); } console.log('='.repeat(70)); diff --git a/scripts/preinstall-check.mjs b/scripts/preinstall-check.mjs index 5b025e15e2..4f90a7853c 100644 --- a/scripts/preinstall-check.mjs +++ b/scripts/preinstall-check.mjs @@ -147,7 +147,7 @@ function parseVersionRange(range, version) { const parts = range.split(',').map(p => p.trim()); for (const part of parts) { - const match = part.match(/^([<>=]+)\s*(.+)$/); + const match = part.match(/^([<>=]+)\s*(\S+)$/); if (!match) { if (part === version) return true; continue; @@ -179,7 +179,8 @@ function compareVersions(a, b) { function extractVersionNumber(versionSpec) { if (!versionSpec) return null; // Remove ^, ~, >=, <=, >, <, = prefixes - const match = versionSpec.match(/[\d]+\.[\d]+\.[\d]+(?:-[\w.]+)?/); + // Use atomic pattern to prevent backtracking: match version then optional prerelease + const match = versionSpec.match(/(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*))?/); return match ? match[0] : null; }