@paretools/shared
Advanced tools
| /** | ||
| * Shared input-validation limits for all Pare MCP servers. | ||
| * | ||
| * These provide defense-in-depth against DoS via extremely long inputs. | ||
| * Applied to Zod input schemas with `.max()` — output schemas are NOT | ||
| * constrained because their size is determined by tool output, not user input. | ||
| */ | ||
| export declare const INPUT_LIMITS: { | ||
| /** Maximum length for any general string parameter (64 KB). */ | ||
| readonly STRING_MAX: 65536; | ||
| /** Maximum items in any array parameter. */ | ||
| readonly ARRAY_MAX: 1000; | ||
| /** Maximum length for file system paths. */ | ||
| readonly PATH_MAX: 4096; | ||
| /** Maximum length for commit messages (generous to allow long messages). */ | ||
| readonly MESSAGE_MAX: 72000; | ||
| /** Maximum length for short identifiers (branch names, package names, etc.). */ | ||
| readonly SHORT_STRING_MAX: 255; | ||
| }; | ||
| //# sourceMappingURL=limits.d.ts.map |
| {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;IACvB,+DAA+D;;IAG/D,4CAA4C;;IAG5C,4CAA4C;;IAG5C,4EAA4E;;IAG5E,gFAAgF;;CAExE,CAAC"} |
| /** | ||
| * Shared input-validation limits for all Pare MCP servers. | ||
| * | ||
| * These provide defense-in-depth against DoS via extremely long inputs. | ||
| * Applied to Zod input schemas with `.max()` — output schemas are NOT | ||
| * constrained because their size is determined by tool output, not user input. | ||
| */ | ||
| export const INPUT_LIMITS = { | ||
| /** Maximum length for any general string parameter (64 KB). */ | ||
| STRING_MAX: 65_536, | ||
| /** Maximum items in any array parameter. */ | ||
| ARRAY_MAX: 1_000, | ||
| /** Maximum length for file system paths. */ | ||
| PATH_MAX: 4_096, | ||
| /** Maximum length for commit messages (generous to allow long messages). */ | ||
| MESSAGE_MAX: 72_000, | ||
| /** Maximum length for short identifiers (branch names, package names, etc.). */ | ||
| SHORT_STRING_MAX: 255, | ||
| }; | ||
| //# sourceMappingURL=limits.js.map |
| {"version":3,"file":"limits.js","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,+DAA+D;IAC/D,UAAU,EAAE,MAAM;IAElB,4CAA4C;IAC5C,SAAS,EAAE,KAAK;IAEhB,4CAA4C;IAC5C,QAAQ,EAAE,KAAK;IAEf,4EAA4E;IAC5E,WAAW,EAAE,MAAM;IAEnB,gFAAgF;IAChF,gBAAgB,EAAE,GAAG;CACb,CAAC"} |
| /** | ||
| * 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. | ||
| * | ||
| * Replacements: | ||
| * /home/<user>/... → ~/... | ||
| * /Users/<user>/... → ~/... | ||
| * /root/... → ~/... | ||
| * C:\Users\<user>\... → ~\... | ||
| */ | ||
| export declare function sanitizeErrorOutput(text: string): string; | ||
| //# sourceMappingURL=sanitize.d.ts.map |
| {"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"} |
| /** | ||
| * 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. | ||
| * | ||
| * Replacements: | ||
| * /home/<user>/... → ~/... | ||
| * /Users/<user>/... → ~/... | ||
| * /root/... → ~/... | ||
| * C:\Users\<user>\... → ~\... | ||
| */ | ||
| export function sanitizeErrorOutput(text) { | ||
| // Unix: /home/<username>/rest → ~/rest | ||
| let result = text.replace(/\/home\/[^/\s]+\//g, "~/"); | ||
| // macOS: /Users/<username>/rest → ~/rest | ||
| result = result.replace(/\/Users\/[^/\s]+\//g, "~/"); | ||
| // Unix: /root/rest → ~/rest | ||
| result = result.replace(/\/root\//g, "~/"); | ||
| // Windows: C:\Users\<username>\rest → ~\rest (with escaped or literal backslashes) | ||
| result = result.replace(/[A-Z]:\\Users\\[^\\:\s]+\\/gi, "~\\"); | ||
| return result; | ||
| } | ||
| //# sourceMappingURL=sanitize.js.map |
| {"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"} |
+4
-2
@@ -1,6 +0,8 @@ | ||
| export { dualOutput } from "./output.js"; | ||
| export { run, type RunResult, type RunOptions } from "./runner.js"; | ||
| export { dualOutput, estimateTokens, compactDualOutput } from "./output.js"; | ||
| export { run, escapeCmdArg, type RunResult, type RunOptions } from "./runner.js"; | ||
| export { stripAnsi } from "./ansi.js"; | ||
| export { assertNoFlagInjection, assertAllowedCommand } from "./validation.js"; | ||
| export { INPUT_LIMITS } from "./limits.js"; | ||
| export { sanitizeErrorOutput } from "./sanitize.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,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,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,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"} |
+4
-2
@@ -1,5 +0,7 @@ | ||
| export { dualOutput } from "./output.js"; | ||
| export { run } from "./runner.js"; | ||
| export { dualOutput, estimateTokens, compactDualOutput } from "./output.js"; | ||
| export { run, escapeCmdArg } from "./runner.js"; | ||
| export { stripAnsi } from "./ansi.js"; | ||
| export { assertNoFlagInjection, assertAllowedCommand } from "./validation.js"; | ||
| export { INPUT_LIMITS } from "./limits.js"; | ||
| export { sanitizeErrorOutput } from "./sanitize.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,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,GAAG,EAAmC,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,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"} |
+20
-0
@@ -12,2 +12,22 @@ import type { ToolOutput } from "./types.js"; | ||
| export declare function dualOutput<T>(data: T, humanFormat: (d: T) => string): ToolOutput<T>; | ||
| /** | ||
| * Estimates the token count of a string using the ~4 chars/token heuristic. | ||
| */ | ||
| export declare function estimateTokens(text: string): number; | ||
| /** | ||
| * Creates a dual-output response with automatic compact mode. | ||
| * | ||
| * Compares the token cost of the full structured JSON against the raw CLI stdout. | ||
| * When the structured output would use more tokens than raw, applies a compact | ||
| * projection to reduce the schema. Setting `forceFullSchema` to true always | ||
| * returns the full (non-compact) data. | ||
| * | ||
| * @param data - The full structured data parsed from CLI output. | ||
| * @param rawStdout - The ANSI-stripped stdout from the CLI command. | ||
| * @param humanFormat - Formatter for full data (used when not compacting). | ||
| * @param compactMap - Projects full data into a compact shape. | ||
| * @param compactFormat - Formatter for compact data. | ||
| * @param forceFullSchema - When true, skip auto-detection and return full data. | ||
| */ | ||
| export declare function compactDualOutput<T, C>(data: T, rawStdout: string, humanFormat: (d: T) => string, compactMap: (d: T) => C, compactFormat: (d: C) => string, forceFullSchema: boolean): ToolOutput<T | C>; | ||
| //# sourceMappingURL=output.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAKnF"} | ||
| {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAKnF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EACpC,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,EAC7B,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EACvB,aAAa,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,EAC/B,eAAe,EAAE,OAAO,GACvB,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAcnB"} |
+33
-0
@@ -16,2 +16,35 @@ /** | ||
| } | ||
| /** | ||
| * Estimates the token count of a string using the ~4 chars/token heuristic. | ||
| */ | ||
| export function estimateTokens(text) { | ||
| return Math.ceil(text.length / 4); | ||
| } | ||
| /** | ||
| * Creates a dual-output response with automatic compact mode. | ||
| * | ||
| * Compares the token cost of the full structured JSON against the raw CLI stdout. | ||
| * When the structured output would use more tokens than raw, applies a compact | ||
| * projection to reduce the schema. Setting `forceFullSchema` to true always | ||
| * returns the full (non-compact) data. | ||
| * | ||
| * @param data - The full structured data parsed from CLI output. | ||
| * @param rawStdout - The ANSI-stripped stdout from the CLI command. | ||
| * @param humanFormat - Formatter for full data (used when not compacting). | ||
| * @param compactMap - Projects full data into a compact shape. | ||
| * @param compactFormat - Formatter for compact data. | ||
| * @param forceFullSchema - When true, skip auto-detection and return full data. | ||
| */ | ||
| export function compactDualOutput(data, rawStdout, humanFormat, compactMap, compactFormat, forceFullSchema) { | ||
| if (forceFullSchema) { | ||
| return dualOutput(data, humanFormat); | ||
| } | ||
| const structuredTokens = estimateTokens(JSON.stringify(data)); | ||
| const rawTokens = estimateTokens(rawStdout); | ||
| if (structuredTokens >= rawTokens) { | ||
| const compact = compactMap(data); | ||
| return dualOutput(compact, compactFormat); | ||
| } | ||
| return dualOutput(data, humanFormat); | ||
| } | ||
| //# sourceMappingURL=output.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAI,IAAO,EAAE,WAA6B;IAClE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,iBAAiB,EAAE,IAAI;KACxB,CAAC;AACJ,CAAC"} | ||
| {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAI,IAAO,EAAE,WAA6B;IAClE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,iBAAiB,EAAE,IAAI;KACxB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAO,EACP,SAAiB,EACjB,WAA6B,EAC7B,UAAuB,EACvB,aAA+B,EAC/B,eAAwB;IAExB,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,UAAU,CAAC,IAAI,EAAE,WAAW,CAAsB,CAAC;IAC5D,CAAC;IAED,MAAM,gBAAgB,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IAE5C,IAAI,gBAAgB,IAAI,SAAS,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,UAAU,CAAC,OAAO,EAAE,aAAa,CAAsB,CAAC;IACjE,CAAC;IAED,OAAO,UAAU,CAAC,IAAI,EAAE,WAAW,CAAsB,CAAC;AAC5D,CAAC"} |
+18
-0
@@ -14,2 +14,20 @@ /** Options for the command runner, including working directory, timeout, and environment overrides. */ | ||
| /** | ||
| * Escapes a single argument for safe use with cmd.exe on Windows. | ||
| * | ||
| * When `shell: true` is used with `execFile` on Windows, Node.js wraps each | ||
| * argument in double quotes. Inside double quotes cmd.exe still interprets: | ||
| * - `%VAR%` for environment variable expansion | ||
| * - `^` as the escape character itself | ||
| * - `&`, `|`, `<`, `>` as pipeline / redirection operators | ||
| * | ||
| * We neutralise these by: | ||
| * 1. Replacing `%` with `%%` (disables env-var expansion inside quotes). | ||
| * 2. Prefixing `^`, `&`, `|`, `<`, `>`, `!` with the cmd.exe escape char `^`. | ||
| * (`!` must be escaped to prevent delayed expansion of `!VAR!`.) | ||
| * | ||
| * Parentheses `(` `)` are NOT escaped here because inside double-quoted | ||
| * strings they are literal characters and do not affect grouping. | ||
| */ | ||
| export declare function escapeCmdArg(arg: string): string; | ||
| /** | ||
| * Executes a command and returns cleaned output with ANSI codes stripped. | ||
@@ -16,0 +34,0 @@ * Uses execFile (not exec) to avoid shell injection. On Windows, shell is |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAGA,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;CAC9B;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;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAkDtF"} | ||
| {"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;CAC9B;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;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAiBhD;AAED;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAkEtF"} |
+46
-3
| import { execFile } from "node:child_process"; | ||
| import { stripAnsi } from "./ansi.js"; | ||
| import { sanitizeErrorOutput } from "./sanitize.js"; | ||
| /** | ||
| * Escapes a single argument for safe use with cmd.exe on Windows. | ||
| * | ||
| * When `shell: true` is used with `execFile` on Windows, Node.js wraps each | ||
| * argument in double quotes. Inside double quotes cmd.exe still interprets: | ||
| * - `%VAR%` for environment variable expansion | ||
| * - `^` as the escape character itself | ||
| * - `&`, `|`, `<`, `>` as pipeline / redirection operators | ||
| * | ||
| * We neutralise these by: | ||
| * 1. Replacing `%` with `%%` (disables env-var expansion inside quotes). | ||
| * 2. Prefixing `^`, `&`, `|`, `<`, `>`, `!` with the cmd.exe escape char `^`. | ||
| * (`!` must be escaped to prevent delayed expansion of `!VAR!`.) | ||
| * | ||
| * Parentheses `(` `)` are NOT escaped here because inside double-quoted | ||
| * strings they are literal characters and do not affect grouping. | ||
| */ | ||
| export function escapeCmdArg(arg) { | ||
| // Step 1: Escape % → %% to prevent %VAR% expansion | ||
| let escaped = arg.replace(/%/g, "%%"); | ||
| // Step 2: Caret-escape cmd.exe metacharacters that are dangerous | ||
| // even inside double quotes. The caret itself must be escaped first | ||
| // so that carets we insert are not themselves re-escaped. | ||
| escaped = escaped.replace(/\^/g, "^^"); | ||
| escaped = escaped.replace(/&/g, "^&"); | ||
| escaped = escaped.replace(/\|/g, "^|"); | ||
| escaped = escaped.replace(/</g, "^<"); | ||
| escaped = escaped.replace(/>/g, "^>"); | ||
| // Step 3: Escape ! to prevent delayed expansion (!VAR!) | ||
| escaped = escaped.replace(/!/g, "^!"); | ||
| return escaped; | ||
| } | ||
| /** | ||
| * Executes a command and returns cleaned output with ANSI codes stripped. | ||
@@ -14,5 +47,9 @@ * Uses execFile (not exec) to avoid shell injection. On Windows, shell is | ||
| return new Promise((resolve, reject) => { | ||
| execFile(cmd, args, { | ||
| // On Windows with shell mode, escape cmd.exe metacharacters to prevent | ||
| // environment variable expansion and command injection via special chars. | ||
| // See escapeCmdArg() for details on what is escaped and why. | ||
| const safeArgs = process.platform === "win32" ? args.map(escapeCmdArg) : args; | ||
| execFile(cmd, safeArgs, { | ||
| cwd: opts?.cwd, | ||
| timeout: opts?.timeout ?? 30_000, | ||
| timeout: opts?.timeout ?? 60_000, | ||
| env: opts?.env ? { ...process.env, ...opts.env } : undefined, | ||
@@ -33,2 +70,8 @@ maxBuffer: 10 * 1024 * 1024, // 10 MB | ||
| } | ||
| // 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 | ||
@@ -44,3 +87,3 @@ const cleanStderr = stripAnsi(stderr); | ||
| stdout: stripAnsi(stdout), | ||
| stderr: stripAnsi(stderr), | ||
| stderr: sanitizeErrorOutput(stripAnsi(stderr)), | ||
| }); | ||
@@ -47,0 +90,0 @@ }); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAgBtC;;;;;;;;GAQG;AACH,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,IAAc,EAAE,IAAiB;IAChE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CACN,GAAG,EACH,IAAI,EACJ;YACE,GAAG,EAAE,IAAI,EAAE,GAAG;YACd,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,MAAM;YAChC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;YAC5D,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,QAAQ;YACrC,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;SACpC,EACD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,KAAK,GAAG,KAA8B,CAAC;gBAE7C,wDAAwD;gBACxD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC5B,MAAM,CACJ,IAAI,KAAK,CACP,uBAAuB,GAAG,uDAAuD,CAClF,CACF,CAAC;oBACF,OAAO;gBACT,CAAC;gBACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBACtD,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;oBAC5E,OAAO;gBACT,CAAC;gBAED,4DAA4D;gBAC5D,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;gBACtC,IAAI,WAAW,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;oBAC9C,MAAM,CACJ,IAAI,KAAK,CACP,uBAAuB,GAAG,uDAAuD,CAClF,CACF,CAAC;oBACF,OAAO;gBACT,CAAC;YACH,CAAC;YAED,OAAO,CAAC;gBACN,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACvE,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC;gBACzB,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC;aAC1B,CAAC,CAAC;QACL,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"} | ||
| {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"} |
+13
-1
@@ -7,4 +7,16 @@ /** | ||
| export declare function assertNoFlagInjection(value: string, paramName: string): void; | ||
| /** Validates that a command is in the allowlist of safe build tools to prevent arbitrary command execution. */ | ||
| /** | ||
| * Validates that a command is in the allowlist of safe build tools to prevent arbitrary command execution. | ||
| * | ||
| * Known limitation: only the basename is checked, so a path like `/tmp/evil/npm` would pass. | ||
| * Rejecting paths entirely would break legitimate use cases (NixOS store paths, `C:\Program Files\...`, | ||
| * non-PATH installs). Exploiting this requires placing a malicious binary on disk, which already | ||
| * implies the system is compromised. | ||
| * | ||
| * Security note: When a full path is provided (containing `/` or `\`), a warning is logged because | ||
| * basename-only validation cannot guarantee the binary at that path is the genuine tool. An attacker | ||
| * with write access to the filesystem could place a malicious binary at a path like `/tmp/evil/npm`. | ||
| * The warning serves as an audit trail for security-conscious deployments. | ||
| */ | ||
| export declare function assertAllowedCommand(command: string): void; | ||
| //# sourceMappingURL=validation.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAM5E;AAiCD,+GAA+G;AAC/G,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAe1D"} | ||
| {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAM5E;AAiCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAwB1D"} |
+20
-2
@@ -7,3 +7,3 @@ /** | ||
| export function assertNoFlagInjection(value, paramName) { | ||
| if (value.startsWith("-")) { | ||
| if (value.trimStart().startsWith("-")) { | ||
| throw new Error(`Invalid ${paramName}: "${value}". Values must not start with "-" to prevent argument injection.`); | ||
@@ -42,3 +42,15 @@ } | ||
| ]); | ||
| /** Validates that a command is in the allowlist of safe build tools to prevent arbitrary command execution. */ | ||
| /** | ||
| * Validates that a command is in the allowlist of safe build tools to prevent arbitrary command execution. | ||
| * | ||
| * Known limitation: only the basename is checked, so a path like `/tmp/evil/npm` would pass. | ||
| * Rejecting paths entirely would break legitimate use cases (NixOS store paths, `C:\Program Files\...`, | ||
| * non-PATH installs). Exploiting this requires placing a malicious binary on disk, which already | ||
| * implies the system is compromised. | ||
| * | ||
| * Security note: When a full path is provided (containing `/` or `\`), a warning is logged because | ||
| * basename-only validation cannot guarantee the binary at that path is the genuine tool. An attacker | ||
| * with write access to the filesystem could place a malicious binary at a path like `/tmp/evil/npm`. | ||
| * The warning serves as an audit trail for security-conscious deployments. | ||
| */ | ||
| export function assertAllowedCommand(command) { | ||
@@ -55,3 +67,9 @@ // Extract the base command name (handle paths like /usr/bin/npm or C:\npm.cmd) | ||
| } | ||
| // Warn when a full path is used — basename-only validation cannot verify the actual binary | ||
| if (command.includes("/") || command.includes("\\")) { | ||
| console.warn(`[pare:security] Command uses a full path: "${command}". ` + | ||
| `Only the basename "${base}" was validated against the allowlist. ` + | ||
| `Ensure this path points to a trusted binary.`); | ||
| } | ||
| } | ||
| //# sourceMappingURL=validation.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa,EAAE,SAAiB;IACpE,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,WAAW,SAAS,MAAM,KAAK,kEAAkE,CAClG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACrC,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,KAAK;IACL,KAAK;IACL,OAAO;IACP,IAAI;IACJ,QAAQ;IACR,SAAS;IACT,KAAK;IACL,SAAS;IACT,MAAM;IACN,SAAS;IACT,QAAQ;IACR,OAAO;IACP,IAAI;IACJ,OAAO;CACR,CAAC,CAAC;AAEH,+GAA+G;AAC/G,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,+EAA+E;IAC/E,MAAM,IAAI,GACR,OAAO;SACJ,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,EAAE;QACN,EAAE,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IAEhD,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,YAAY,OAAO,+CAA+C;YAChE,YAAY,CAAC,GAAG,sBAAsB,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9D,CAAC;IACJ,CAAC;AACH,CAAC"} | ||
| {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa,EAAE,SAAiB;IACpE,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,WAAW,SAAS,MAAM,KAAK,kEAAkE,CAClG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACrC,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,KAAK;IACL,KAAK;IACL,OAAO;IACP,IAAI;IACJ,QAAQ;IACR,SAAS;IACT,KAAK;IACL,SAAS;IACT,MAAM;IACN,SAAS;IACT,QAAQ;IACR,OAAO;IACP,IAAI;IACJ,OAAO;CACR,CAAC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,+EAA+E;IAC/E,MAAM,IAAI,GACR,OAAO;SACJ,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,EAAE;QACN,EAAE,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IAEhD,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,YAAY,OAAO,+CAA+C;YAChE,YAAY,CAAC,GAAG,sBAAsB,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9D,CAAC;IACJ,CAAC;IAED,2FAA2F;IAC3F,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,OAAO,CAAC,IAAI,CACV,8CAA8C,OAAO,KAAK;YACxD,sBAAsB,IAAI,yCAAyC;YACnE,8CAA8C,CACjD,CAAC;IACJ,CAAC;AACH,CAAC"} |
+3
-3
| { | ||
| "name": "@paretools/shared", | ||
| "version": "0.5.0", | ||
| "version": "0.6.0", | ||
| "description": "Shared utilities for Pare MCP servers", | ||
@@ -17,3 +17,3 @@ "license": "MIT", | ||
| ], | ||
| "homepage": "https://github.com/Dave-London/pare/tree/main/packages/shared", | ||
| "homepage": "https://github.com/Dave-London/Pare/tree/main/packages/shared", | ||
| "engines": { | ||
@@ -34,3 +34,3 @@ "node": ">=20" | ||
| "type": "git", | ||
| "url": "https://github.com/Dave-London/pare.git", | ||
| "url": "https://github.com/Dave-London/Pare.git", | ||
| "directory": "packages/shared" | ||
@@ -37,0 +37,0 @@ }, |
+4
-4
| # @paretools/shared | ||
| [](https://www.npmjs.com/package/@paretools/shared) | ||
| [](https://github.com/Dave-London/pare/blob/main/LICENSE) | ||
| [](https://github.com/Dave-London/Pare/blob/main/LICENSE) | ||
| Shared utilities for [Pare](https://github.com/Dave-London/pare) MCP servers. | ||
| Shared utilities for [Pare](https://github.com/Dave-London/Pare) MCP servers. | ||
@@ -24,6 +24,6 @@ ## Exports | ||
| - [Pare monorepo](https://github.com/Dave-London/pare) | ||
| - [Pare monorepo](https://github.com/Dave-London/Pare) | ||
| ## License | ||
| [MIT](https://github.com/Dave-London/pare/blob/main/LICENSE) | ||
| [MIT](https://github.com/Dave-London/Pare/blob/main/LICENSE) |
31209
88.97%35
29.63%409
116.4%