From b1353cb79b0866db5d571e626637c36803a3e94a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 03:28:59 +0000 Subject: [PATCH] fix: prevent command injection in init scripts by validating install dir - Added `validateInstallDir` function to `src/ops.mjs` to enforce strict allowlist for directory names (alphanumeric, hyphens, underscores, slashes, periods). - Integrated validation into `resolveConfiguration` to sanitize both interactive and mock inputs. - Added comprehensive unit tests in `test/install_dir_validation.test.mjs`. This fixes a vulnerability where malicious directory names could inject shell commands into `package.json` scripts during initialization. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- src/ops.mjs | 23 +++++- test/install_dir_validation.test.mjs | 109 +++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 test/install_dir_validation.test.mjs diff --git a/src/ops.mjs b/src/ops.mjs index 880fb1f..cb94df4 100644 --- a/src/ops.mjs +++ b/src/ops.mjs @@ -152,11 +152,26 @@ async function interactiveInit(cwd) { } } -async function resolveConfiguration(cwd, mockAnswers) { - if (mockAnswers) { - return mockAnswers; +function validateInstallDir(dir) { + if (dir === '.') return; + + // Strict validation to prevent command injection + // Only allow alphanumeric, hyphens, underscores, slashes, and periods. + if (!/^[a-zA-Z0-9_\-\/\.]+$/.test(dir)) { + throw new Error(`Invalid directory name: "${dir}". Only alphanumeric characters, hyphens, underscores, slashes, and periods are allowed.`); } - return await interactiveInit(cwd); +} + +async function resolveConfiguration(cwd, mockAnswers) { + let config; + if (mockAnswers) { + config = mockAnswers; + } else { + config = await interactiveInit(cwd); + } + + validateInstallDir(config.installDir); + return config; } function ensureInstallDirectory(paths, force, installDir) { diff --git a/test/install_dir_validation.test.mjs b/test/install_dir_validation.test.mjs new file mode 100644 index 0000000..9568803 --- /dev/null +++ b/test/install_dir_validation.test.mjs @@ -0,0 +1,109 @@ +import { test } from 'node:test'; +import assert from 'node:assert'; +import { cmdInit } from '../src/ops.mjs'; +import fs from 'node:fs'; +import path from 'node:path'; + +const TEST_DIR = 'test_validation_env'; + +test('cmdInit should validate install directory name', async (t) => { + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + } + fs.mkdirSync(TEST_DIR); + + const mockPkg = { name: "host", scripts: {} }; + fs.writeFileSync(path.join(TEST_DIR, 'package.json'), JSON.stringify(mockPkg)); + + await t.test('should reject directory with semicolon', async () => { + await assert.rejects(async () => { + await cmdInit(false, TEST_DIR, { + installDir: 'torch; echo pwned', + namespace: 'ns', + relays: [] + }); + }, /Invalid directory name/); + }); + + await t.test('should reject directory with spaces', async () => { + await assert.rejects(async () => { + await cmdInit(false, TEST_DIR, { + installDir: 'torch dir', + namespace: 'ns', + relays: [] + }); + }, /Invalid directory name/); + }); + + await t.test('should reject directory with quotes', async () => { + await assert.rejects(async () => { + await cmdInit(false, TEST_DIR, { + installDir: 'torch"dir', + namespace: 'ns', + relays: [] + }); + }, /Invalid directory name/); + }); + + await t.test('should reject directory with backticks', async () => { + await assert.rejects(async () => { + await cmdInit(false, TEST_DIR, { + installDir: 'torch`dir', + namespace: 'ns', + relays: [] + }); + }, /Invalid directory name/); + }); + + await t.test('should reject directory with $', async () => { + await assert.rejects(async () => { + await cmdInit(false, TEST_DIR, { + installDir: 'torch$dir', + namespace: 'ns', + relays: [] + }); + }, /Invalid directory name/); + }); + + await t.test('should accept valid directory names', async () => { + try { + await cmdInit(false, TEST_DIR, { + installDir: 'valid-dir_123', + namespace: 'ns', + relays: [] + }); + } catch (e) { + // It might fail because of other initialization steps, but shouldn't fail validation + assert.notMatch(e.message, /Invalid directory name/); + } + }); + + await t.test('should accept "."', async () => { + try { + await cmdInit(false, TEST_DIR, { + installDir: '.', + namespace: 'ns', + relays: [] + }); + } catch (e) { + assert.notMatch(e.message, /Invalid directory name/); + } + }); + + await t.test('should accept nested paths with slashes', async () => { + try { + await cmdInit(false, TEST_DIR, { + installDir: 'path/to/torch', + namespace: 'ns', + relays: [] + }); + } catch (e) { + assert.notMatch(e.message, /Invalid directory name/); + } + }); + + // Cleanup + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + } +});