@paretools/shared
Advanced tools
| /** | ||
| * Security policy controls — opt-in hardening via environment variables. | ||
| * | ||
| * All controls follow the same global/per-server precedence pattern as | ||
| * tool filtering (see tool-filter.ts): | ||
| * | ||
| * 1. `PARE_{SETTING}` — global, applies to all servers. | ||
| * 2. `PARE_{SERVER}_{SETTING}` — per-server override. | ||
| * 3. No env var → permissive (default, no restriction). | ||
| * | ||
| * Global takes precedence over per-server when both are set. | ||
| * | ||
| * ## Available Controls | ||
| * | ||
| * ### PARE_ALLOWED_COMMANDS / PARE_{SERVER}_ALLOWED_COMMANDS | ||
| * Comma-separated list of allowed command names. | ||
| * When set, only listed commands may be executed. | ||
| * Example: `PARE_PROCESS_ALLOWED_COMMANDS=node,python,make` | ||
| * Example: `PARE_ALLOWED_COMMANDS=node,python,git,npm` (all servers) | ||
| * | ||
| * ### PARE_ALLOWED_ROOTS / PARE_{SERVER}_ALLOWED_ROOTS | ||
| * Comma-separated list of allowed root directories. | ||
| * When set, all path/cwd parameters must be under one of these roots. | ||
| * Example: `PARE_ALLOWED_ROOTS=/home/user/projects,/tmp/builds` | ||
| * Example: `PARE_PROCESS_ALLOWED_ROOTS=/home/user/safe-dir` | ||
| * | ||
| * ### PARE_BUILD_STRICT_PATH | ||
| * When set to "true", the build server rejects path-qualified commands | ||
| * (e.g., `/tmp/evil/npm`) and only allows bare command names resolved via PATH. | ||
| */ | ||
| /** | ||
| * Asserts that a command is allowed by the ALLOWED_COMMANDS policy. | ||
| * No-op when the policy is not configured (permissive default). | ||
| * | ||
| * @param command - The command to check (e.g., "node", "python"). | ||
| * @param serverName - Server identifier for per-server overrides (e.g., "process"). | ||
| * @throws Error if the command is not in the allowlist. | ||
| */ | ||
| export declare function assertAllowedByPolicy(command: string, serverName: string): void; | ||
| /** | ||
| * Asserts that a working directory path is under one of the allowed roots. | ||
| * No-op when the policy is not configured (permissive default). | ||
| * | ||
| * @param targetPath - The path/cwd to validate. | ||
| * @param serverName - Server identifier for per-server overrides (e.g., "process"). | ||
| * @throws Error if the path is outside all allowed roots. | ||
| */ | ||
| export declare function assertAllowedRoot(targetPath: string, serverName: string): void; | ||
| /** | ||
| * Asserts that a command does not contain path separators (strict path mode). | ||
| * Used by build server when PARE_BUILD_STRICT_PATH=true. | ||
| * | ||
| * @param command - The command to check. | ||
| * @throws Error if the command contains `/` or `\`. | ||
| */ | ||
| export declare function assertNoPathQualifiedCommand(command: string): void; | ||
| //# sourceMappingURL=policy.d.ts.map |
| {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AA+BH;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAmB/E;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAoB9E;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CASlE"} |
+118
| /** | ||
| * Security policy controls — opt-in hardening via environment variables. | ||
| * | ||
| * All controls follow the same global/per-server precedence pattern as | ||
| * tool filtering (see tool-filter.ts): | ||
| * | ||
| * 1. `PARE_{SETTING}` — global, applies to all servers. | ||
| * 2. `PARE_{SERVER}_{SETTING}` — per-server override. | ||
| * 3. No env var → permissive (default, no restriction). | ||
| * | ||
| * Global takes precedence over per-server when both are set. | ||
| * | ||
| * ## Available Controls | ||
| * | ||
| * ### PARE_ALLOWED_COMMANDS / PARE_{SERVER}_ALLOWED_COMMANDS | ||
| * Comma-separated list of allowed command names. | ||
| * When set, only listed commands may be executed. | ||
| * Example: `PARE_PROCESS_ALLOWED_COMMANDS=node,python,make` | ||
| * Example: `PARE_ALLOWED_COMMANDS=node,python,git,npm` (all servers) | ||
| * | ||
| * ### PARE_ALLOWED_ROOTS / PARE_{SERVER}_ALLOWED_ROOTS | ||
| * Comma-separated list of allowed root directories. | ||
| * When set, all path/cwd parameters must be under one of these roots. | ||
| * Example: `PARE_ALLOWED_ROOTS=/home/user/projects,/tmp/builds` | ||
| * Example: `PARE_PROCESS_ALLOWED_ROOTS=/home/user/safe-dir` | ||
| * | ||
| * ### PARE_BUILD_STRICT_PATH | ||
| * When set to "true", the build server rejects path-qualified commands | ||
| * (e.g., `/tmp/evil/npm`) and only allows bare command names resolved via PATH. | ||
| */ | ||
| import { resolve, normalize } from "node:path"; | ||
| /** | ||
| * Reads a policy env var with global/per-server precedence. | ||
| * Global (`PARE_{setting}`) wins over per-server (`PARE_{SERVER}_{setting}`). | ||
| * Returns undefined when neither is set (no restriction). | ||
| */ | ||
| function readPolicyVar(serverName, setting) { | ||
| const global = process.env[`PARE_${setting}`]; | ||
| if (global !== undefined) | ||
| return global; | ||
| const envKey = `PARE_${serverName.toUpperCase().replace(/-/g, "_")}_${setting}`; | ||
| return process.env[envKey]; | ||
| } | ||
| /** | ||
| * Parses a comma-separated env var value into a trimmed Set. | ||
| * Returns undefined if the raw value is undefined or empty. | ||
| */ | ||
| function parseList(raw) { | ||
| if (raw === undefined || raw.trim() === "") | ||
| return undefined; | ||
| return new Set(raw | ||
| .split(",") | ||
| .map((s) => s.trim()) | ||
| .filter(Boolean)); | ||
| } | ||
| /** | ||
| * Asserts that a command is allowed by the ALLOWED_COMMANDS policy. | ||
| * No-op when the policy is not configured (permissive default). | ||
| * | ||
| * @param command - The command to check (e.g., "node", "python"). | ||
| * @param serverName - Server identifier for per-server overrides (e.g., "process"). | ||
| * @throws Error if the command is not in the allowlist. | ||
| */ | ||
| export function assertAllowedByPolicy(command, serverName) { | ||
| const raw = readPolicyVar(serverName, "ALLOWED_COMMANDS"); | ||
| const allowed = parseList(raw); | ||
| if (!allowed) | ||
| return; // No policy — everything allowed | ||
| // Extract basename for comparison (handle paths like /usr/bin/node) | ||
| const base = command | ||
| .replace(/\\/g, "/") | ||
| .split("/") | ||
| .pop() | ||
| ?.replace(/\.(cmd|exe|bat|sh)$/i, "") ?? ""; | ||
| if (!allowed.has(base) && !allowed.has(command)) { | ||
| throw new Error(`Command "${command}" is not allowed by ALLOWED_COMMANDS policy. ` + | ||
| `Allowed: ${[...allowed].sort().join(", ")}`); | ||
| } | ||
| } | ||
| /** | ||
| * Asserts that a working directory path is under one of the allowed roots. | ||
| * No-op when the policy is not configured (permissive default). | ||
| * | ||
| * @param targetPath - The path/cwd to validate. | ||
| * @param serverName - Server identifier for per-server overrides (e.g., "process"). | ||
| * @throws Error if the path is outside all allowed roots. | ||
| */ | ||
| export function assertAllowedRoot(targetPath, serverName) { | ||
| const raw = readPolicyVar(serverName, "ALLOWED_ROOTS"); | ||
| const roots = parseList(raw); | ||
| if (!roots) | ||
| return; // No policy — all paths allowed | ||
| const normalizedTarget = normalize(resolve(targetPath)); | ||
| for (const root of roots) { | ||
| const normalizedRoot = normalize(resolve(root)); | ||
| if (normalizedTarget === normalizedRoot || | ||
| normalizedTarget.startsWith(normalizedRoot + (process.platform === "win32" ? "\\" : "/"))) { | ||
| return; // Path is under an allowed root | ||
| } | ||
| } | ||
| throw new Error(`Path "${targetPath}" is outside allowed roots. ` + `Allowed roots: ${[...roots].join(", ")}`); | ||
| } | ||
| /** | ||
| * Asserts that a command does not contain path separators (strict path mode). | ||
| * Used by build server when PARE_BUILD_STRICT_PATH=true. | ||
| * | ||
| * @param command - The command to check. | ||
| * @throws Error if the command contains `/` or `\`. | ||
| */ | ||
| export function assertNoPathQualifiedCommand(command) { | ||
| if (process.env.PARE_BUILD_STRICT_PATH === "true") { | ||
| if (command.includes("/") || command.includes("\\")) { | ||
| throw new Error(`Path-qualified commands are not allowed when PARE_BUILD_STRICT_PATH is enabled. ` + | ||
| `Use a bare command name (e.g., "npm" not "${command}") that resolves via PATH.`); | ||
| } | ||
| } | ||
| } | ||
| //# sourceMappingURL=policy.js.map |
| {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"} |
+1
-0
@@ -8,3 +8,4 @@ export { dualOutput, estimateTokens, compactDualOutput } from "./output.js"; | ||
| export { shouldRegisterTool } from "./tool-filter.js"; | ||
| export { assertAllowedByPolicy, assertAllowedRoot, assertNoPathQualifiedCommand, } from "./policy.js"; | ||
| export type { ToolOutput } from "./types.js"; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"} |
+1
-0
@@ -8,2 +8,3 @@ export { dualOutput, estimateTokens, compactDualOutput } from "./output.js"; | ||
| export { shouldRegisterTool } from "./tool-filter.js"; | ||
| export { assertAllowedByPolicy, assertAllowedRoot, assertNoPathQualifiedCommand, } from "./policy.js"; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAE,YAAY,EAAmC,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC"} | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAE,YAAY,EAAmC,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,4BAA4B,GAC7B,MAAM,aAAa,CAAC"} |
+9
-4
@@ -14,2 +14,4 @@ /** Options for the command runner, including working directory, timeout, and environment overrides. */ | ||
| shell?: boolean; | ||
| /** Maximum combined stdout+stderr buffer size in bytes. Defaults to 10 MB. */ | ||
| maxBuffer?: number; | ||
| } | ||
@@ -44,7 +46,10 @@ /** Result of a command execution, containing the exit code and ANSI-stripped stdout/stderr. */ | ||
| * Executes a command and returns cleaned output with ANSI codes stripped. | ||
| * Uses execFile (not exec) to avoid shell injection. On Windows, shell is | ||
| * enabled so that .cmd/.bat wrappers (like npx) can be executed — args are | ||
| * still passed as an array so they remain properly escaped. | ||
| * Uses spawn (not exec) to avoid shell injection. The child is spawned in its | ||
| * own process group (`detached: true` on Unix) so that on timeout we can kill | ||
| * the entire group — preventing orphaned grandchild processes. | ||
| * | ||
| * Throws on system-level errors (command not found, permission denied). | ||
| * On Windows, shell is enabled so that .cmd/.bat wrappers (like npx) can be | ||
| * executed — args are still passed as an array so they remain properly escaped. | ||
| * | ||
| * Throws on system-level errors (command not found, permission denied, timeout). | ||
| * Normal non-zero exit codes are returned in the result, not thrown. | ||
@@ -51,0 +56,0 @@ */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAIA,uGAAuG;AACvG,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;;mFAE+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;qFAEiF;IACjF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,+FAA+F;AAC/F,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAqChD;AAED;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA8EtF"} | ||
| {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAIA,uGAAuG;AACvG,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;;mFAE+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;qFAEiF;IACjF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA6BD,+FAA+F;AAC/F,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAqChD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA+ItF"} |
+128
-35
@@ -1,5 +0,35 @@ | ||
| import { execFile } from "node:child_process"; | ||
| import { spawn, execFile as nodeExecFile } from "node:child_process"; | ||
| import { stripAnsi } from "./ansi.js"; | ||
| import { sanitizeErrorOutput } from "./sanitize.js"; | ||
| /** | ||
| * Kills an entire process group (Unix) or process tree (Windows). | ||
| * | ||
| * On Unix, `spawn({ detached: true })` calls `setsid(2)`, making the child | ||
| * a process group leader. Sending a signal to `-pid` reaches every process | ||
| * in that group, including grandchildren. | ||
| * | ||
| * On Windows, `taskkill /T /F` walks the native process tree. | ||
| * | ||
| * ESRCH (process already exited) is silently ignored. | ||
| */ | ||
| function killProcessGroup(pid) { | ||
| if (process.platform === "win32") { | ||
| try { | ||
| nodeExecFile("taskkill", ["/pid", String(pid), "/T", "/F"]); | ||
| } | ||
| catch { | ||
| /* best-effort */ | ||
| } | ||
| } | ||
| else { | ||
| try { | ||
| process.kill(-pid, "SIGTERM"); | ||
| } | ||
| catch (err) { | ||
| if (err.code !== "ESRCH") | ||
| throw err; | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Escapes a single argument for safe use with cmd.exe on Windows. | ||
@@ -59,7 +89,10 @@ * | ||
| * Executes a command and returns cleaned output with ANSI codes stripped. | ||
| * Uses execFile (not exec) to avoid shell injection. On Windows, shell is | ||
| * enabled so that .cmd/.bat wrappers (like npx) can be executed — args are | ||
| * still passed as an array so they remain properly escaped. | ||
| * Uses spawn (not exec) to avoid shell injection. The child is spawned in its | ||
| * own process group (`detached: true` on Unix) so that on timeout we can kill | ||
| * the entire group — preventing orphaned grandchild processes. | ||
| * | ||
| * Throws on system-level errors (command not found, permission denied). | ||
| * On Windows, shell is enabled so that .cmd/.bat wrappers (like npx) can be | ||
| * executed — args are still passed as an array so they remain properly escaped. | ||
| * | ||
| * Throws on system-level errors (command not found, permission denied, timeout). | ||
| * Normal non-zero exit codes are returned in the result, not thrown. | ||
@@ -77,37 +110,97 @@ */ | ||
| const safeArgs = useShell && process.platform === "win32" ? args.map(escapeCmdArg) : args; | ||
| const child = execFile(cmd, safeArgs, { | ||
| const child = spawn(cmd, safeArgs, { | ||
| cwd: opts?.cwd, | ||
| timeout: opts?.timeout ?? 60_000, | ||
| env: opts?.env ? { ...process.env, ...opts.env } : undefined, | ||
| maxBuffer: 10 * 1024 * 1024, // 10 MB | ||
| shell: useShell, | ||
| }, (error, stdout, stderr) => { | ||
| if (error) { | ||
| const errno = error; | ||
| // Unix: direct ENOENT from execFile (no shell wrapping) | ||
| if (errno.code === "ENOENT") { | ||
| reject(new Error(`Command not found: "${cmd}". Ensure it is installed and available in your PATH.`)); | ||
| return; | ||
| } | ||
| if (errno.code === "EACCES" || errno.code === "EPERM") { | ||
| reject(new Error(`Permission denied executing "${cmd}": ${errno.message}`)); | ||
| return; | ||
| } | ||
| // Timeout: execFile killed the child after the configured timeout. | ||
| // Surface this clearly instead of silently returning exitCode 1. | ||
| if (error.killed && error.signal) { | ||
| reject(new Error(`Command "${cmd}" timed out after ${opts?.timeout ?? 60_000}ms and was killed (${error.signal}).`)); | ||
| return; | ||
| } | ||
| // Windows: cmd.exe masks ENOENT — detect via stderr message | ||
| const cleanStderr = stripAnsi(stderr); | ||
| if (cleanStderr.includes("is not recognized")) { | ||
| reject(new Error(`Command not found: "${cmd}". Ensure it is installed and available in your PATH.`)); | ||
| return; | ||
| } | ||
| // Unix: creates a new process group via setsid(2) so we can kill the | ||
| // entire group on timeout (prevents orphaned grandchild processes). | ||
| // Windows: skip — detached on Windows creates a visible console window, | ||
| // not a process group. Use taskkill /T /F instead. | ||
| detached: process.platform !== "win32", | ||
| windowsHide: true, | ||
| stdio: ["pipe", "pipe", "pipe"], | ||
| }); | ||
| // --- Buffer accumulation (replaces execFile's maxBuffer) --- | ||
| const maxBuffer = opts?.maxBuffer ?? 10 * 1024 * 1024; // 10 MB | ||
| const stdoutChunks = []; | ||
| const stderrChunks = []; | ||
| let stdoutLen = 0; | ||
| let stderrLen = 0; | ||
| let bufferExceeded = false; | ||
| child.stdout?.on("data", (chunk) => { | ||
| stdoutLen += chunk.length; | ||
| if (stdoutLen + stderrLen > maxBuffer && !bufferExceeded) { | ||
| bufferExceeded = true; | ||
| killProcessGroup(child.pid); | ||
| return; | ||
| } | ||
| if (!bufferExceeded) | ||
| stdoutChunks.push(chunk); | ||
| }); | ||
| child.stderr?.on("data", (chunk) => { | ||
| stderrLen += chunk.length; | ||
| if (stdoutLen + stderrLen > maxBuffer && !bufferExceeded) { | ||
| bufferExceeded = true; | ||
| killProcessGroup(child.pid); | ||
| return; | ||
| } | ||
| if (!bufferExceeded) | ||
| stderrChunks.push(chunk); | ||
| }); | ||
| // --- Timeout handling (replaces execFile's timeout) --- | ||
| let timedOut = false; | ||
| let timeoutSignal; | ||
| const timeoutMs = opts?.timeout ?? 60_000; | ||
| const timer = setTimeout(() => { | ||
| timedOut = true; | ||
| timeoutSignal = "SIGTERM"; | ||
| killProcessGroup(child.pid); | ||
| }, timeoutMs); | ||
| // --- Settlement guard (prevents double-resolve from close + error) --- | ||
| let settled = false; | ||
| // Handle spawn errors (ENOENT, EACCES on Unix — spawn emits these as | ||
| // 'error' events, unlike execFile which passes them to the callback). | ||
| child.on("error", (err) => { | ||
| clearTimeout(timer); | ||
| if (settled) | ||
| return; | ||
| settled = true; | ||
| if (err.code === "ENOENT") { | ||
| reject(new Error(`Command not found: "${cmd}". Ensure it is installed and available in your PATH.`)); | ||
| return; | ||
| } | ||
| if (err.code === "EACCES" || err.code === "EPERM") { | ||
| reject(new Error(`Permission denied executing "${cmd}": ${err.message}`)); | ||
| return; | ||
| } | ||
| reject(err); | ||
| }); | ||
| // Resolve on 'close' (not 'exit') — close fires after ALL stdio streams | ||
| // are flushed, guaranteeing no late data events. | ||
| child.on("close", (code, signal) => { | ||
| clearTimeout(timer); | ||
| if (settled) | ||
| return; | ||
| settled = true; | ||
| const stdout = stripAnsi(Buffer.concat(stdoutChunks).toString("utf-8")); | ||
| const stderr = sanitizeErrorOutput(stripAnsi(Buffer.concat(stderrChunks).toString("utf-8"))); | ||
| // Timeout: we killed the process group after the configured timeout. | ||
| if (timedOut) { | ||
| reject(new Error(`Command "${cmd}" timed out after ${timeoutMs}ms and was killed (${timeoutSignal ?? signal ?? "SIGTERM"}).`)); | ||
| return; | ||
| } | ||
| // maxBuffer exceeded | ||
| if (bufferExceeded) { | ||
| reject(new Error(`Command "${cmd}" output exceeded maxBuffer (${maxBuffer} bytes) and was killed.`)); | ||
| return; | ||
| } | ||
| // Windows: cmd.exe masks ENOENT — detect via stderr message | ||
| if (code !== 0 && stderr.includes("is not recognized")) { | ||
| reject(new Error(`Command not found: "${cmd}". Ensure it is installed and available in your PATH.`)); | ||
| return; | ||
| } | ||
| resolve({ | ||
| exitCode: error ? (typeof error.code === "number" ? error.code : 1) : 0, | ||
| stdout: stripAnsi(stdout), | ||
| stderr: sanitizeErrorOutput(stripAnsi(stderr)), | ||
| exitCode: code ?? 1, | ||
| stdout, | ||
| stderr, | ||
| }); | ||
@@ -114,0 +207,0 @@ }); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"} |
| /** | ||
| * Sanitizes error output by replacing sensitive filesystem paths with | ||
| * home-relative equivalents. This prevents leaking usernames and absolute | ||
| * home directory paths in error messages returned to MCP clients. | ||
| * Sanitizes error output by replacing sensitive filesystem paths. | ||
| * | ||
| * Replacements: | ||
| * Default mode (home paths only): | ||
| * /home/<user>/... → ~/... | ||
@@ -11,4 +9,10 @@ * /Users/<user>/... → ~/... | ||
| * C:\Users\<user>\... → ~\... | ||
| * | ||
| * Broad mode (PARE_SANITIZE_ALL_PATHS=true): | ||
| * Also redacts other absolute paths outside home directories: | ||
| * /etc/..., /var/..., /opt/..., C:\Program Files\..., etc. | ||
| * These are replaced with <redacted-path>/basename to preserve | ||
| * the filename while hiding directory structure. | ||
| */ | ||
| export declare function sanitizeErrorOutput(text: string): string; | ||
| //# sourceMappingURL=sanitize.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAcxD"} | ||
| {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA6BxD"} |
+21
-4
| /** | ||
| * Sanitizes error output by replacing sensitive filesystem paths with | ||
| * home-relative equivalents. This prevents leaking usernames and absolute | ||
| * home directory paths in error messages returned to MCP clients. | ||
| * Sanitizes error output by replacing sensitive filesystem paths. | ||
| * | ||
| * Replacements: | ||
| * Default mode (home paths only): | ||
| * /home/<user>/... → ~/... | ||
@@ -11,2 +9,8 @@ * /Users/<user>/... → ~/... | ||
| * C:\Users\<user>\... → ~\... | ||
| * | ||
| * Broad mode (PARE_SANITIZE_ALL_PATHS=true): | ||
| * Also redacts other absolute paths outside home directories: | ||
| * /etc/..., /var/..., /opt/..., C:\Program Files\..., etc. | ||
| * These are replaced with <redacted-path>/basename to preserve | ||
| * the filename while hiding directory structure. | ||
| */ | ||
@@ -22,4 +26,17 @@ export function sanitizeErrorOutput(text) { | ||
| result = result.replace(/[A-Z]:\\Users\\[^\\:\s]+\\/gi, "~\\"); | ||
| // Broad mode: redact remaining absolute paths | ||
| if (process.env.PARE_SANITIZE_ALL_PATHS === "true") { | ||
| // Unix absolute paths not already handled (e.g., /etc/foo/bar → <redacted>/bar) | ||
| result = result.replace(/\/(?:etc|var|opt|usr|tmp|srv|snap|nix)\/[^\s:]+/g, (match) => { | ||
| const basename = match.split("/").pop() ?? ""; | ||
| return `<redacted-path>/${basename}`; | ||
| }); | ||
| // Windows non-user absolute paths (e.g., C:\Program Files\foo → <redacted>\foo) | ||
| result = result.replace(/[A-Z]:\\(?!Users\\)[^\s:]+/gi, (match) => { | ||
| const basename = match.split("\\").pop() ?? ""; | ||
| return `<redacted-path>\\${basename}`; | ||
| }); | ||
| } | ||
| return result; | ||
| } | ||
| //# sourceMappingURL=sanitize.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,uCAAuC;IACvC,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC;IAEtD,yCAAyC;IACzC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;IAErD,4BAA4B;IAC5B,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAE3C,mFAAmF;IACnF,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;IAE/D,OAAO,MAAM,CAAC;AAChB,CAAC"} | ||
| {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,uCAAuC;IACvC,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC;IAEtD,yCAAyC;IACzC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;IAErD,4BAA4B;IAC5B,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAE3C,mFAAmF;IACnF,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;IAE/D,8CAA8C;IAC9C,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,MAAM,EAAE,CAAC;QACnD,gFAAgF;QAChF,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,kDAAkD,EAAE,CAAC,KAAK,EAAE,EAAE;YACpF,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;YAC9C,OAAO,mBAAmB,QAAQ,EAAE,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,gFAAgF;QAChF,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,8BAA8B,EAAE,CAAC,KAAK,EAAE,EAAE;YAChE,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;YAC/C,OAAO,oBAAoB,QAAQ,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"} |
| /** | ||
| * Validates that a string argument is safe to pass as a positional argument to a CLI tool. | ||
| * Prevents flag injection attacks (e.g., passing "--output=/etc/passwd" as a ref name). | ||
| * See: CVE-2025-68144, CVE-2025-68145 | ||
| * See: [CVE-2025-68144](https://nvd.nist.gov/vuln/detail/CVE-2025-68144), [CVE-2025-68145](https://nvd.nist.gov/vuln/detail/CVE-2025-68145) | ||
| */ | ||
@@ -6,0 +6,0 @@ export declare function assertNoFlagInjection(value: string, paramName: string): void; |
| /** | ||
| * Validates that a string argument is safe to pass as a positional argument to a CLI tool. | ||
| * Prevents flag injection attacks (e.g., passing "--output=/etc/passwd" as a ref name). | ||
| * See: CVE-2025-68144, CVE-2025-68145 | ||
| * See: [CVE-2025-68144](https://nvd.nist.gov/vuln/detail/CVE-2025-68144), [CVE-2025-68145](https://nvd.nist.gov/vuln/detail/CVE-2025-68145) | ||
| */ | ||
@@ -6,0 +6,0 @@ export function assertNoFlagInjection(value, paramName) { |
+1
-1
| { | ||
| "name": "@paretools/shared", | ||
| "version": "0.8.1", | ||
| "version": "0.8.2", | ||
| "description": "Shared utilities for Pare MCP servers", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances
56950
46.96%43
10.26%813
56.65%7
133.33%