From 5280f85e958b2d457958e97e5e738b165ad760ed Mon Sep 17 00:00:00 2001 From: mrxlolcat Date: Sun, 24 May 2026 08:59:33 +0000 Subject: [PATCH 1/2] fix(security): restrict npm in module install commands to install/ci only --- src/spark_cli/runtime_policy.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/spark_cli/runtime_policy.py b/src/spark_cli/runtime_policy.py index 08a73198c..ff2efc40c 100644 --- a/src/spark_cli/runtime_policy.py +++ b/src/spark_cli/runtime_policy.py @@ -35,7 +35,16 @@ def resolve_runtime_executable(name: str) -> str: ) +_NPM_ALLOWED_SUBCOMMANDS: frozenset[str] = frozenset({"install", "ci"}) + + def npm_runtime_command_argv(args: list[str]) -> list[str]: + if not args or args[0].lower() not in _NPM_ALLOWED_SUBCOMMANDS: + allowed = ", ".join(sorted(_NPM_ALLOWED_SUBCOMMANDS)) + raise SystemExit( + f"npm subcommand {args[0]!r} is not permitted in Spark module commands. " + f"Allowed: {allowed}." + ) npm_path = resolve_runtime_executable("npm") if os.name == "nt" and os.path.splitext(npm_path)[1].lower() in {".cmd", ".bat"}: npm_dir = os.path.dirname(npm_path) From afd7381de789262695282e81d83ba6ac408a4cb9 Mon Sep 17 00:00:00 2001 From: mrxlolcat Date: Thu, 30 Jul 2026 06:29:13 +0000 Subject: [PATCH 2/2] fix(security): scope npm allowlist to install commands --- src/spark_cli/cli.py | 4 ++-- src/spark_cli/runtime_policy.py | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/spark_cli/cli.py b/src/spark_cli/cli.py index 3a4686649..54fef1626 100644 --- a/src/spark_cli/cli.py +++ b/src/spark_cli/cli.py @@ -36,7 +36,7 @@ import tomllib from .env_files import normalize_env_file_value -from .runtime_policy import run_runtime_command, runtime_command_argv, split_single_argv_command +from .runtime_policy import npm_install_command_argv, run_runtime_command, runtime_command_argv, split_single_argv_command from .security.approval import CommandContext, approval_required_for_command from .security.prompt_injection import scan_prompt_injection_text from .security.url_policy import UrlPolicy, validate_url_safety @@ -7354,7 +7354,7 @@ def install_command_argv(command: str) -> list[str]: if executable == "uv" and len(parts) >= 2 and parts[1] == "pip": return [str(Path(sys.executable)), "-m", "pip", *parts[2:]] if executable == "npm": - return [resolve_install_executable("npm"), *parts[1:]] + return npm_install_command_argv(parts[1:]) raise SystemExit( "Unsupported install command executable. Allowed install commands must start with " "python, python3, pip, pip3, uv pip, or npm." diff --git a/src/spark_cli/runtime_policy.py b/src/spark_cli/runtime_policy.py index ff2efc40c..fee9b81e2 100644 --- a/src/spark_cli/runtime_policy.py +++ b/src/spark_cli/runtime_policy.py @@ -35,14 +35,14 @@ def resolve_runtime_executable(name: str) -> str: ) -_NPM_ALLOWED_SUBCOMMANDS: frozenset[str] = frozenset({"install", "ci"}) +_NPM_INSTALL_ALLOWED_SUBCOMMANDS: frozenset[str] = frozenset({"install", "ci"}) -def npm_runtime_command_argv(args: list[str]) -> list[str]: - if not args or args[0].lower() not in _NPM_ALLOWED_SUBCOMMANDS: - allowed = ", ".join(sorted(_NPM_ALLOWED_SUBCOMMANDS)) +def npm_install_command_argv(args: list[str]) -> list[str]: + if not args or args[0].lower() not in _NPM_INSTALL_ALLOWED_SUBCOMMANDS: + allowed = ", ".join(sorted(_NPM_INSTALL_ALLOWED_SUBCOMMANDS)) raise SystemExit( - f"npm subcommand {args[0]!r} is not permitted in Spark module commands. " + f"npm subcommand {args[0]!r} is not permitted in Spark install commands. " f"Allowed: {allowed}." ) npm_path = resolve_runtime_executable("npm") @@ -63,7 +63,8 @@ def runtime_command_argv(command: str) -> list[str]: if executable == "node": return [resolve_runtime_executable("node"), *parts[1:]] if executable == "npm": - return npm_runtime_command_argv(parts[1:]) + npm_path = resolve_runtime_executable("npm") + return [npm_path, *parts[1:]] if executable == "uv" and len(parts) >= 2 and parts[1] == "run": return [resolve_runtime_executable("uv"), *parts[1:]] raise SystemExit(