llm-cli-gateway 1.5.29 → 1.5.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to the llm-cli-gateway project.
4
4
 
5
+ ## [1.5.31] - 2026-05-25
6
+
7
+ ### Changed
8
+
9
+ - Replace direct dependency on `toml@3.0.0` (single-maintainer, last released 2020) with `smol-toml@^1.6.1` (actively maintained, TypeScript-native, zero deps). Same `parse(text)` API, drop-in across `src/config.ts`, `src/claude-mcp-config.ts`, and `src/model-registry.ts`.
10
+
11
+ ### Security
12
+
13
+ - Add `socket.yml` documenting the rationale for Socket's behavioural alerts (`networkAccess`, `shellAccess`, `usesEval`). Alerts are left visible — not silenced — so downstream consumers can see the maintainer's review context.
14
+ - Expand README "Security Considerations" with a per-alert breakdown mapping each Socket signal to where it lives in the code and why it is bounded.
15
+
16
+ ## [1.5.30] - 2026-05-25
17
+
18
+ ### Fixed
19
+
20
+ - Quote Windows `.cmd` and `.bat` provider shim invocations through `cmd.exe` to preserve paths with spaces and escape command-processor metacharacters in forwarded arguments.
21
+
5
22
  ## [1.5.29] - 2026-05-25
6
23
 
7
24
  ### Fixed
package/README.md CHANGED
@@ -1005,8 +1005,22 @@ The gateway supports concurrent requests across different CLIs. Each request spa
1005
1005
 
1006
1006
  - **Input Validation**: All prompts are validated (min 1 char, max 100k chars)
1007
1007
  - **Command Execution**: Uses `spawn` with separate arguments (not shell execution)
1008
- - **No Eval**: No dynamic code evaluation
1008
+ - **No Eval**: No dynamic code evaluation in our source (see "Socket alerts" below for the transitive `ajv` codegen case)
1009
1009
  - **Sandboxing**: Consider running in containers for production use
1010
+ - **Provenance**: Releases are published with [npm provenance](https://docs.npmjs.com/generating-provenance-statements) via OIDC trusted publishing from GitHub Actions
1011
+
1012
+ ### Socket alerts — context for reviewers
1013
+
1014
+ If you're vetting `llm-cli-gateway` through [Socket](https://socket.dev/npm/package/llm-cli-gateway) or a similar supply-chain scanner, you'll see three behavioural alerts and some dependency-ownership alerts. They are accurate descriptions of what the package does and what it depends on; we've left them visible (not silenced in `socket.yml`) so you don't have to take our word for it. Here's the context for each:
1015
+
1016
+ | Alert | Where | Why it's bounded |
1017
+ |---|---|---|
1018
+ | **Network access** | `src/http-transport.ts` opens an HTTP MCP transport when started via `npm run start:http`. `src/endpoint-exposure.ts` issues a HEAD probe to verify configured public/tunnel URLs. | The transport binds to `127.0.0.1` by default and requires `LLM_GATEWAY_AUTH_TOKEN` to be set. The default stdio MCP entry point (`npm start`) opens no sockets. |
1019
+ | **Shell access** | `src/executor.ts` uses `child_process.spawn(cmd, args, …)` to invoke the underlying LLM CLIs. | `spawn` is called with an argument array and **never** `shell: true`, so there is no shell interpolation path for caller input. The command name is restricted to an allow-list of known CLI binaries (`claude`, `codex`, `gemini`, `grok`, `vibe`). |
1020
+ | **Uses eval** | None in our source. Transitive: `@modelcontextprotocol/sdk` → `ajv@8` uses `new Function(...)` in `ajv/dist/compile/index.js` to compile JSON Schema validators. | This is ajv's standard codegen path. Only known schemas (defined in our source and the MCP SDK) flow into it; no caller-supplied data ever reaches the compiled function body. |
1021
+ | **Dependency ownership** | A handful of small transitive packages (e.g. `bindings` via `better-sqlite3`, `media-typer` via `@modelcontextprotocol/sdk`) trip Socket's "unstable ownership" or "obfuscated code" heuristics. | These are pinned, well-known micro-deps in the Node ecosystem with no known issues. We pin direct override versions of `content-type` and `type-is` in `package.json#overrides`. Our previous direct dependency on `toml@3.0.0` (also single-maintainer, last released 2020) was replaced with the actively-maintained `smol-toml` to reduce inherited risk. |
1022
+
1023
+ See [`socket.yml`](./socket.yml) for the same context in machine-readable form.
1010
1024
 
1011
1025
  ## Contributing
1012
1026
 
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, renameSync, openSync, fsyncSync, closeSync, chmodSync, } from "fs";
2
2
  import { homedir } from "os";
3
3
  import { dirname, join } from "path";
4
- import { parse as parseToml } from "toml";
4
+ import { parse as parseToml } from "smol-toml";
5
5
  export const CLAUDE_MCP_SERVER_NAMES = ["sqry", "exa", "ref_tools", "trstr"];
6
6
  function asStringArray(value) {
7
7
  if (!Array.isArray(value)) {
package/dist/config.js CHANGED
@@ -108,7 +108,7 @@ function readPersistenceFile(configPath, logger) {
108
108
  }
109
109
  try {
110
110
  const require = createRequire(import.meta.url);
111
- const TOML = require("toml");
111
+ const TOML = require("smol-toml");
112
112
  const text = readFileSync(configPath, "utf-8");
113
113
  const parsed = TOML.parse(text);
114
114
  return { raw: parsed?.persistence, sourcePath: configPath };
@@ -19,6 +19,7 @@ export declare function envWithExtendedPath(baseEnv?: NodeJS.ProcessEnv, extende
19
19
  export interface ResolvedSpawnCommand {
20
20
  command: string;
21
21
  args: string[];
22
+ windowsVerbatimArguments?: boolean;
22
23
  }
23
24
  export declare function resolveCommandForSpawn(command: string, args: string[], options?: {
24
25
  envPath?: string;
package/dist/executor.js CHANGED
@@ -139,11 +139,31 @@ export function resolveCommandForSpawn(command, args, options = {}) {
139
139
  if ([".cmd", ".bat"].includes(extname(resolved).toLowerCase())) {
140
140
  return {
141
141
  command: "cmd.exe",
142
- args: ["/d", "/s", "/c", resolved, ...args],
142
+ args: ["/d", "/s", "/c", `"${buildWindowsCmdCommand(resolved, args)}"`],
143
+ windowsVerbatimArguments: true,
143
144
  };
144
145
  }
145
146
  return { command: resolved, args };
146
147
  }
148
+ function buildWindowsCmdCommand(command, args) {
149
+ return [escapeWindowsCmdCommand(command), ...args.map(escapeWindowsCmdArgument)].join(" ");
150
+ }
151
+ const WINDOWS_CMD_META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
152
+ function escapeWindowsCmdCommand(value) {
153
+ return win32.normalize(value).replace(WINDOWS_CMD_META_CHARS, "^$1");
154
+ }
155
+ // CommandLineToArgvW rules: a run of N backslashes before a literal " must be
156
+ // doubled and followed by \" (yielding 2N+1 backslashes total, so the parser
157
+ // strips N and keeps the quote as literal); a run of N backslashes immediately
158
+ // before the closing " must be doubled (2N) so the quote still terminates the
159
+ // arg. Then wrap in quotes and caret-escape cmd.exe metacharacters.
160
+ function escapeWindowsCmdArgument(value) {
161
+ let arg = `${value}`;
162
+ arg = arg.replace(/(\\*)"/g, '$1$1\\"');
163
+ arg = arg.replace(/(\\*)$/, "$1$1");
164
+ arg = `"${arg}"`;
165
+ return arg.replace(WINDOWS_CMD_META_CHARS, "^$1");
166
+ }
147
167
  function resolveWindowsCommandPath(command, envPath) {
148
168
  if (/[\\/]/.test(command)) {
149
169
  return existsSync(command) ? command : null;
@@ -266,6 +286,7 @@ export function spawnCliProcess(command, args, options) {
266
286
  cwd: options.cwd,
267
287
  detached,
268
288
  windowsHide: true,
289
+ windowsVerbatimArguments: resolved.windowsVerbatimArguments,
269
290
  stdio: options.stdio,
270
291
  env: options.env,
271
292
  });
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync, readdirSync, statSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import path from "path";
4
- import { parse as parseToml } from "toml";
4
+ import { parse as parseToml } from "smol-toml";
5
5
  const FALLBACK_INFO = {
6
6
  claude: {
7
7
  description: "Anthropic's Claude Code CLI - best for code generation, analysis, and agentic coding tasks",
@@ -106,6 +106,7 @@ function runCommand(command, args, timeoutMs) {
106
106
  input: "",
107
107
  timeout: timeoutMs,
108
108
  windowsHide: true,
109
+ windowsVerbatimArguments: resolved.windowsVerbatimArguments,
109
110
  });
110
111
  const output = sanitizeOutput(`${result.stdout || ""}\n${result.stderr || ""}`);
111
112
  return {
@@ -551,6 +551,7 @@ export function probeInstalledCliContract(cli, timeoutMs = 5_000) {
551
551
  maxBuffer: 1024 * 1024,
552
552
  env,
553
553
  windowsHide: true,
554
+ windowsVerbatimArguments: resolved.windowsVerbatimArguments,
554
555
  });
555
556
  if (result.error) {
556
557
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-cli-gateway",
3
- "version": "1.5.29",
3
+ "version": "1.5.31",
4
4
  "mcpName": "io.github.verivus-oss/llm-cli-gateway",
5
5
  "description": "MCP server providing unified access to Claude Code, Codex, Gemini, Grok, and Mistral Vibe CLIs with session management, retry logic, async job orchestration, durable job results, and cross-LLM validation.",
6
6
  "license": "MIT",
@@ -49,6 +49,7 @@
49
49
  "setup/status.schema.json",
50
50
  "README.md",
51
51
  "CHANGELOG.md",
52
+ "socket.yml",
52
53
  "LICENSE"
53
54
  ],
54
55
  "scripts": {
@@ -83,7 +84,7 @@
83
84
  "@modelcontextprotocol/sdk": "^1.29.0",
84
85
  "better-sqlite3": "^12.10.0",
85
86
  "content-type": "1.0.5",
86
- "toml": "^3.0.0",
87
+ "smol-toml": "^1.6.1",
87
88
  "type-is": "2.0.1",
88
89
  "zod": "^3.23.0"
89
90
  },
package/socket.yml ADDED
@@ -0,0 +1,49 @@
1
+ version: 2
2
+
3
+ # Socket alerts on llm-cli-gateway
4
+ # ---------------------------------
5
+ # This package intentionally triggers three of Socket's behavioural alerts.
6
+ # We do NOT disable them — they are accurate descriptions of what the package
7
+ # does, and silencing them would hide useful signal from anyone evaluating
8
+ # this dependency. The rationale for each is documented inline below and in
9
+ # detail under "Security Considerations" in README.md.
10
+ #
11
+ # networkAccess
12
+ # src/http-transport.ts opens an HTTP MCP transport (createServer/listen).
13
+ # Defaults to 127.0.0.1, auth-token gated (LLM_GATEWAY_AUTH_TOKEN).
14
+ # src/endpoint-exposure.ts also issues a HEAD probe when verifying
15
+ # tunnel reachability — opt-in via the start:http entry point only.
16
+ #
17
+ # shellAccess
18
+ # src/executor.ts uses child_process.spawn(cmd, args, { ... }) with a
19
+ # fixed allow-list of CLI binaries (claude / codex / gemini / grok /
20
+ # vibe). shell:true is never set; arguments are passed as an array, so
21
+ # there is no shell interpolation path for user input. Spawning these
22
+ # CLIs is the entire purpose of the package.
23
+ #
24
+ # usesEval
25
+ # Not in our source. Transitive via @modelcontextprotocol/sdk → ajv@8,
26
+ # which compiles JSON Schema validators using `new Function(...)`.
27
+ # This is ajv's standard codegen path; no caller-supplied data flows
28
+ # into the compiled function body.
29
+
30
+ issueRules:
31
+ # Defaults from Socket. Listed explicitly so future contributors see what
32
+ # is enforced rather than relying on implicit defaults.
33
+ malware: true
34
+ troll: true
35
+ didYouMean: true
36
+ installScripts: true
37
+ telemetry: true
38
+ hasNativeCode: true # better-sqlite3 — known and expected
39
+ shellScriptOverride: true
40
+ gitDependency: true
41
+ httpDependency: true
42
+ invalidPackageJSON: true
43
+ unresolvedRequire: true
44
+
45
+ githubApp:
46
+ enabled: true
47
+ pullRequestAlertsEnabled: true
48
+ dependencyOverviewEnabled: true
49
+ projectReportsEnabled: true