@paretools/shared 0.8.0 → 0.8.2
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/policy.d.ts +57 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +118 -0
- package/dist/policy.js.map +1 -0
- package/dist/runner.d.ts +9 -4
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +128 -35
- package/dist/runner.js.map +1 -1
- package/dist/sanitize.d.ts +8 -4
- package/dist/sanitize.d.ts.map +1 -1
- package/dist/sanitize.js +21 -4
- package/dist/sanitize.js.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,5 +5,6 @@ export { assertNoFlagInjection, assertAllowedCommand } from "./validation.js";
|
|
|
5
5
|
export { INPUT_LIMITS } from "./limits.js";
|
|
6
6
|
export { sanitizeErrorOutput } from "./sanitize.js";
|
|
7
7
|
export { shouldRegisterTool } from "./tool-filter.js";
|
|
8
|
+
export { assertAllowedByPolicy, assertAllowedRoot, assertNoPathQualifiedCommand, } from "./policy.js";
|
|
8
9
|
export type { ToolOutput } from "./types.js";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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"}
|
|
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,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,4 +5,5 @@ export { assertNoFlagInjection, assertAllowedCommand } from "./validation.js";
|
|
|
5
5
|
export { INPUT_LIMITS } from "./limits.js";
|
|
6
6
|
export { sanitizeErrorOutput } from "./sanitize.js";
|
|
7
7
|
export { shouldRegisterTool } from "./tool-filter.js";
|
|
8
|
+
export { assertAllowedByPolicy, assertAllowedRoot, assertNoPathQualifiedCommand, } from "./policy.js";
|
|
8
9
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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"}
|
|
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;AACtD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,4BAA4B,GAC7B,MAAM,aAAa,CAAC"}
|
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security policy controls — opt-in hardening via environment variables.
|
|
3
|
+
*
|
|
4
|
+
* All controls follow the same global/per-server precedence pattern as
|
|
5
|
+
* tool filtering (see tool-filter.ts):
|
|
6
|
+
*
|
|
7
|
+
* 1. `PARE_{SETTING}` — global, applies to all servers.
|
|
8
|
+
* 2. `PARE_{SERVER}_{SETTING}` — per-server override.
|
|
9
|
+
* 3. No env var → permissive (default, no restriction).
|
|
10
|
+
*
|
|
11
|
+
* Global takes precedence over per-server when both are set.
|
|
12
|
+
*
|
|
13
|
+
* ## Available Controls
|
|
14
|
+
*
|
|
15
|
+
* ### PARE_ALLOWED_COMMANDS / PARE_{SERVER}_ALLOWED_COMMANDS
|
|
16
|
+
* Comma-separated list of allowed command names.
|
|
17
|
+
* When set, only listed commands may be executed.
|
|
18
|
+
* Example: `PARE_PROCESS_ALLOWED_COMMANDS=node,python,make`
|
|
19
|
+
* Example: `PARE_ALLOWED_COMMANDS=node,python,git,npm` (all servers)
|
|
20
|
+
*
|
|
21
|
+
* ### PARE_ALLOWED_ROOTS / PARE_{SERVER}_ALLOWED_ROOTS
|
|
22
|
+
* Comma-separated list of allowed root directories.
|
|
23
|
+
* When set, all path/cwd parameters must be under one of these roots.
|
|
24
|
+
* Example: `PARE_ALLOWED_ROOTS=/home/user/projects,/tmp/builds`
|
|
25
|
+
* Example: `PARE_PROCESS_ALLOWED_ROOTS=/home/user/safe-dir`
|
|
26
|
+
*
|
|
27
|
+
* ### PARE_BUILD_STRICT_PATH
|
|
28
|
+
* When set to "true", the build server rejects path-qualified commands
|
|
29
|
+
* (e.g., `/tmp/evil/npm`) and only allows bare command names resolved via PATH.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Asserts that a command is allowed by the ALLOWED_COMMANDS policy.
|
|
33
|
+
* No-op when the policy is not configured (permissive default).
|
|
34
|
+
*
|
|
35
|
+
* @param command - The command to check (e.g., "node", "python").
|
|
36
|
+
* @param serverName - Server identifier for per-server overrides (e.g., "process").
|
|
37
|
+
* @throws Error if the command is not in the allowlist.
|
|
38
|
+
*/
|
|
39
|
+
export declare function assertAllowedByPolicy(command: string, serverName: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Asserts that a working directory path is under one of the allowed roots.
|
|
42
|
+
* No-op when the policy is not configured (permissive default).
|
|
43
|
+
*
|
|
44
|
+
* @param targetPath - The path/cwd to validate.
|
|
45
|
+
* @param serverName - Server identifier for per-server overrides (e.g., "process").
|
|
46
|
+
* @throws Error if the path is outside all allowed roots.
|
|
47
|
+
*/
|
|
48
|
+
export declare function assertAllowedRoot(targetPath: string, serverName: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Asserts that a command does not contain path separators (strict path mode).
|
|
51
|
+
* Used by build server when PARE_BUILD_STRICT_PATH=true.
|
|
52
|
+
*
|
|
53
|
+
* @param command - The command to check.
|
|
54
|
+
* @throws Error if the command contains `/` or `\`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function assertNoPathQualifiedCommand(command: string): void;
|
|
57
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"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"}
|
package/dist/policy.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security policy controls — opt-in hardening via environment variables.
|
|
3
|
+
*
|
|
4
|
+
* All controls follow the same global/per-server precedence pattern as
|
|
5
|
+
* tool filtering (see tool-filter.ts):
|
|
6
|
+
*
|
|
7
|
+
* 1. `PARE_{SETTING}` — global, applies to all servers.
|
|
8
|
+
* 2. `PARE_{SERVER}_{SETTING}` — per-server override.
|
|
9
|
+
* 3. No env var → permissive (default, no restriction).
|
|
10
|
+
*
|
|
11
|
+
* Global takes precedence over per-server when both are set.
|
|
12
|
+
*
|
|
13
|
+
* ## Available Controls
|
|
14
|
+
*
|
|
15
|
+
* ### PARE_ALLOWED_COMMANDS / PARE_{SERVER}_ALLOWED_COMMANDS
|
|
16
|
+
* Comma-separated list of allowed command names.
|
|
17
|
+
* When set, only listed commands may be executed.
|
|
18
|
+
* Example: `PARE_PROCESS_ALLOWED_COMMANDS=node,python,make`
|
|
19
|
+
* Example: `PARE_ALLOWED_COMMANDS=node,python,git,npm` (all servers)
|
|
20
|
+
*
|
|
21
|
+
* ### PARE_ALLOWED_ROOTS / PARE_{SERVER}_ALLOWED_ROOTS
|
|
22
|
+
* Comma-separated list of allowed root directories.
|
|
23
|
+
* When set, all path/cwd parameters must be under one of these roots.
|
|
24
|
+
* Example: `PARE_ALLOWED_ROOTS=/home/user/projects,/tmp/builds`
|
|
25
|
+
* Example: `PARE_PROCESS_ALLOWED_ROOTS=/home/user/safe-dir`
|
|
26
|
+
*
|
|
27
|
+
* ### PARE_BUILD_STRICT_PATH
|
|
28
|
+
* When set to "true", the build server rejects path-qualified commands
|
|
29
|
+
* (e.g., `/tmp/evil/npm`) and only allows bare command names resolved via PATH.
|
|
30
|
+
*/
|
|
31
|
+
import { resolve, normalize } from "node:path";
|
|
32
|
+
/**
|
|
33
|
+
* Reads a policy env var with global/per-server precedence.
|
|
34
|
+
* Global (`PARE_{setting}`) wins over per-server (`PARE_{SERVER}_{setting}`).
|
|
35
|
+
* Returns undefined when neither is set (no restriction).
|
|
36
|
+
*/
|
|
37
|
+
function readPolicyVar(serverName, setting) {
|
|
38
|
+
const global = process.env[`PARE_${setting}`];
|
|
39
|
+
if (global !== undefined)
|
|
40
|
+
return global;
|
|
41
|
+
const envKey = `PARE_${serverName.toUpperCase().replace(/-/g, "_")}_${setting}`;
|
|
42
|
+
return process.env[envKey];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parses a comma-separated env var value into a trimmed Set.
|
|
46
|
+
* Returns undefined if the raw value is undefined or empty.
|
|
47
|
+
*/
|
|
48
|
+
function parseList(raw) {
|
|
49
|
+
if (raw === undefined || raw.trim() === "")
|
|
50
|
+
return undefined;
|
|
51
|
+
return new Set(raw
|
|
52
|
+
.split(",")
|
|
53
|
+
.map((s) => s.trim())
|
|
54
|
+
.filter(Boolean));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Asserts that a command is allowed by the ALLOWED_COMMANDS policy.
|
|
58
|
+
* No-op when the policy is not configured (permissive default).
|
|
59
|
+
*
|
|
60
|
+
* @param command - The command to check (e.g., "node", "python").
|
|
61
|
+
* @param serverName - Server identifier for per-server overrides (e.g., "process").
|
|
62
|
+
* @throws Error if the command is not in the allowlist.
|
|
63
|
+
*/
|
|
64
|
+
export function assertAllowedByPolicy(command, serverName) {
|
|
65
|
+
const raw = readPolicyVar(serverName, "ALLOWED_COMMANDS");
|
|
66
|
+
const allowed = parseList(raw);
|
|
67
|
+
if (!allowed)
|
|
68
|
+
return; // No policy — everything allowed
|
|
69
|
+
// Extract basename for comparison (handle paths like /usr/bin/node)
|
|
70
|
+
const base = command
|
|
71
|
+
.replace(/\\/g, "/")
|
|
72
|
+
.split("/")
|
|
73
|
+
.pop()
|
|
74
|
+
?.replace(/\.(cmd|exe|bat|sh)$/i, "") ?? "";
|
|
75
|
+
if (!allowed.has(base) && !allowed.has(command)) {
|
|
76
|
+
throw new Error(`Command "${command}" is not allowed by ALLOWED_COMMANDS policy. ` +
|
|
77
|
+
`Allowed: ${[...allowed].sort().join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Asserts that a working directory path is under one of the allowed roots.
|
|
82
|
+
* No-op when the policy is not configured (permissive default).
|
|
83
|
+
*
|
|
84
|
+
* @param targetPath - The path/cwd to validate.
|
|
85
|
+
* @param serverName - Server identifier for per-server overrides (e.g., "process").
|
|
86
|
+
* @throws Error if the path is outside all allowed roots.
|
|
87
|
+
*/
|
|
88
|
+
export function assertAllowedRoot(targetPath, serverName) {
|
|
89
|
+
const raw = readPolicyVar(serverName, "ALLOWED_ROOTS");
|
|
90
|
+
const roots = parseList(raw);
|
|
91
|
+
if (!roots)
|
|
92
|
+
return; // No policy — all paths allowed
|
|
93
|
+
const normalizedTarget = normalize(resolve(targetPath));
|
|
94
|
+
for (const root of roots) {
|
|
95
|
+
const normalizedRoot = normalize(resolve(root));
|
|
96
|
+
if (normalizedTarget === normalizedRoot ||
|
|
97
|
+
normalizedTarget.startsWith(normalizedRoot + (process.platform === "win32" ? "\\" : "/"))) {
|
|
98
|
+
return; // Path is under an allowed root
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`Path "${targetPath}" is outside allowed roots. ` + `Allowed roots: ${[...roots].join(", ")}`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Asserts that a command does not contain path separators (strict path mode).
|
|
105
|
+
* Used by build server when PARE_BUILD_STRICT_PATH=true.
|
|
106
|
+
*
|
|
107
|
+
* @param command - The command to check.
|
|
108
|
+
* @throws Error if the command contains `/` or `\`.
|
|
109
|
+
*/
|
|
110
|
+
export function assertNoPathQualifiedCommand(command) {
|
|
111
|
+
if (process.env.PARE_BUILD_STRICT_PATH === "true") {
|
|
112
|
+
if (command.includes("/") || command.includes("\\")) {
|
|
113
|
+
throw new Error(`Path-qualified commands are not allowed when PARE_BUILD_STRICT_PATH is enabled. ` +
|
|
114
|
+
`Use a bare command name (e.g., "npm" not "${command}") that resolves via PATH.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
|
package/dist/runner.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface RunOptions {
|
|
|
11
11
|
* and `false` elsewhere. Set to `false` for native executables (e.g., git)
|
|
12
12
|
* whose args contain characters that cmd.exe would misinterpret (like `<>`). */
|
|
13
13
|
shell?: boolean;
|
|
14
|
+
/** Maximum combined stdout+stderr buffer size in bytes. Defaults to 10 MB. */
|
|
15
|
+
maxBuffer?: number;
|
|
14
16
|
}
|
|
15
17
|
/** Result of a command execution, containing the exit code and ANSI-stripped stdout/stderr. */
|
|
16
18
|
export interface RunResult {
|
|
@@ -40,11 +42,14 @@ export interface RunResult {
|
|
|
40
42
|
export declare function escapeCmdArg(arg: string): string;
|
|
41
43
|
/**
|
|
42
44
|
* Executes a command and returns cleaned output with ANSI codes stripped.
|
|
43
|
-
* Uses
|
|
44
|
-
*
|
|
45
|
-
*
|
|
45
|
+
* Uses spawn (not exec) to avoid shell injection. The child is spawned in its
|
|
46
|
+
* own process group (`detached: true` on Unix) so that on timeout we can kill
|
|
47
|
+
* the entire group — preventing orphaned grandchild processes.
|
|
46
48
|
*
|
|
47
|
-
*
|
|
49
|
+
* On Windows, shell is enabled so that .cmd/.bat wrappers (like npx) can be
|
|
50
|
+
* executed — args are still passed as an array so they remain properly escaped.
|
|
51
|
+
*
|
|
52
|
+
* Throws on system-level errors (command not found, permission denied, timeout).
|
|
48
53
|
* Normal non-zero exit codes are returned in the result, not thrown.
|
|
49
54
|
*/
|
|
50
55
|
export declare function run(cmd: string, args: string[], opts?: RunOptions): Promise<RunResult>;
|
package/dist/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;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"}
|
package/dist/runner.js
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
1
|
+
import { spawn, execFile as nodeExecFile } from "node:child_process";
|
|
2
2
|
import { stripAnsi } from "./ansi.js";
|
|
3
3
|
import { sanitizeErrorOutput } from "./sanitize.js";
|
|
4
|
+
/**
|
|
5
|
+
* Kills an entire process group (Unix) or process tree (Windows).
|
|
6
|
+
*
|
|
7
|
+
* On Unix, `spawn({ detached: true })` calls `setsid(2)`, making the child
|
|
8
|
+
* a process group leader. Sending a signal to `-pid` reaches every process
|
|
9
|
+
* in that group, including grandchildren.
|
|
10
|
+
*
|
|
11
|
+
* On Windows, `taskkill /T /F` walks the native process tree.
|
|
12
|
+
*
|
|
13
|
+
* ESRCH (process already exited) is silently ignored.
|
|
14
|
+
*/
|
|
15
|
+
function killProcessGroup(pid) {
|
|
16
|
+
if (process.platform === "win32") {
|
|
17
|
+
try {
|
|
18
|
+
nodeExecFile("taskkill", ["/pid", String(pid), "/T", "/F"]);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
/* best-effort */
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
try {
|
|
26
|
+
process.kill(-pid, "SIGTERM");
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (err.code !== "ESRCH")
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
4
34
|
/**
|
|
5
35
|
* Escapes a single argument for safe use with cmd.exe on Windows.
|
|
6
36
|
*
|
|
@@ -56,11 +86,14 @@ export function escapeCmdArg(arg) {
|
|
|
56
86
|
}
|
|
57
87
|
/**
|
|
58
88
|
* Executes a command and returns cleaned output with ANSI codes stripped.
|
|
59
|
-
* Uses
|
|
60
|
-
*
|
|
61
|
-
*
|
|
89
|
+
* Uses spawn (not exec) to avoid shell injection. The child is spawned in its
|
|
90
|
+
* own process group (`detached: true` on Unix) so that on timeout we can kill
|
|
91
|
+
* the entire group — preventing orphaned grandchild processes.
|
|
62
92
|
*
|
|
63
|
-
*
|
|
93
|
+
* On Windows, shell is enabled so that .cmd/.bat wrappers (like npx) can be
|
|
94
|
+
* executed — args are still passed as an array so they remain properly escaped.
|
|
95
|
+
*
|
|
96
|
+
* Throws on system-level errors (command not found, permission denied, timeout).
|
|
64
97
|
* Normal non-zero exit codes are returned in the result, not thrown.
|
|
65
98
|
*/
|
|
66
99
|
export function run(cmd, args, opts) {
|
|
@@ -73,41 +106,101 @@ export function run(cmd, args, opts) {
|
|
|
73
106
|
// environment variable expansion and command injection via special chars.
|
|
74
107
|
// See escapeCmdArg() for details on what is escaped and why.
|
|
75
108
|
const safeArgs = useShell && process.platform === "win32" ? args.map(escapeCmdArg) : args;
|
|
76
|
-
const child =
|
|
109
|
+
const child = spawn(cmd, safeArgs, {
|
|
77
110
|
cwd: opts?.cwd,
|
|
78
|
-
timeout: opts?.timeout ?? 60_000,
|
|
79
111
|
env: opts?.env ? { ...process.env, ...opts.env } : undefined,
|
|
80
|
-
maxBuffer: 10 * 1024 * 1024, // 10 MB
|
|
81
112
|
shell: useShell,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
// Unix: creates a new process group via setsid(2) so we can kill the
|
|
114
|
+
// entire group on timeout (prevents orphaned grandchild processes).
|
|
115
|
+
// Windows: skip — detached on Windows creates a visible console window,
|
|
116
|
+
// not a process group. Use taskkill /T /F instead.
|
|
117
|
+
detached: process.platform !== "win32",
|
|
118
|
+
windowsHide: true,
|
|
119
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
120
|
+
});
|
|
121
|
+
// --- Buffer accumulation (replaces execFile's maxBuffer) ---
|
|
122
|
+
const maxBuffer = opts?.maxBuffer ?? 10 * 1024 * 1024; // 10 MB
|
|
123
|
+
const stdoutChunks = [];
|
|
124
|
+
const stderrChunks = [];
|
|
125
|
+
let stdoutLen = 0;
|
|
126
|
+
let stderrLen = 0;
|
|
127
|
+
let bufferExceeded = false;
|
|
128
|
+
child.stdout?.on("data", (chunk) => {
|
|
129
|
+
stdoutLen += chunk.length;
|
|
130
|
+
if (stdoutLen + stderrLen > maxBuffer && !bufferExceeded) {
|
|
131
|
+
bufferExceeded = true;
|
|
132
|
+
killProcessGroup(child.pid);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!bufferExceeded)
|
|
136
|
+
stdoutChunks.push(chunk);
|
|
137
|
+
});
|
|
138
|
+
child.stderr?.on("data", (chunk) => {
|
|
139
|
+
stderrLen += chunk.length;
|
|
140
|
+
if (stdoutLen + stderrLen > maxBuffer && !bufferExceeded) {
|
|
141
|
+
bufferExceeded = true;
|
|
142
|
+
killProcessGroup(child.pid);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!bufferExceeded)
|
|
146
|
+
stderrChunks.push(chunk);
|
|
147
|
+
});
|
|
148
|
+
// --- Timeout handling (replaces execFile's timeout) ---
|
|
149
|
+
let timedOut = false;
|
|
150
|
+
let timeoutSignal;
|
|
151
|
+
const timeoutMs = opts?.timeout ?? 60_000;
|
|
152
|
+
const timer = setTimeout(() => {
|
|
153
|
+
timedOut = true;
|
|
154
|
+
timeoutSignal = "SIGTERM";
|
|
155
|
+
killProcessGroup(child.pid);
|
|
156
|
+
}, timeoutMs);
|
|
157
|
+
// --- Settlement guard (prevents double-resolve from close + error) ---
|
|
158
|
+
let settled = false;
|
|
159
|
+
// Handle spawn errors (ENOENT, EACCES on Unix — spawn emits these as
|
|
160
|
+
// 'error' events, unlike execFile which passes them to the callback).
|
|
161
|
+
child.on("error", (err) => {
|
|
162
|
+
clearTimeout(timer);
|
|
163
|
+
if (settled)
|
|
164
|
+
return;
|
|
165
|
+
settled = true;
|
|
166
|
+
if (err.code === "ENOENT") {
|
|
167
|
+
reject(new Error(`Command not found: "${cmd}". Ensure it is installed and available in your PATH.`));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
171
|
+
reject(new Error(`Permission denied executing "${cmd}": ${err.message}`));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
reject(err);
|
|
175
|
+
});
|
|
176
|
+
// Resolve on 'close' (not 'exit') — close fires after ALL stdio streams
|
|
177
|
+
// are flushed, guaranteeing no late data events.
|
|
178
|
+
child.on("close", (code, signal) => {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
if (settled)
|
|
181
|
+
return;
|
|
182
|
+
settled = true;
|
|
183
|
+
const stdout = stripAnsi(Buffer.concat(stdoutChunks).toString("utf-8"));
|
|
184
|
+
const stderr = sanitizeErrorOutput(stripAnsi(Buffer.concat(stderrChunks).toString("utf-8")));
|
|
185
|
+
// Timeout: we killed the process group after the configured timeout.
|
|
186
|
+
if (timedOut) {
|
|
187
|
+
reject(new Error(`Command "${cmd}" timed out after ${timeoutMs}ms and was killed (${timeoutSignal ?? signal ?? "SIGTERM"}).`));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// maxBuffer exceeded
|
|
191
|
+
if (bufferExceeded) {
|
|
192
|
+
reject(new Error(`Command "${cmd}" output exceeded maxBuffer (${maxBuffer} bytes) and was killed.`));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Windows: cmd.exe masks ENOENT — detect via stderr message
|
|
196
|
+
if (code !== 0 && stderr.includes("is not recognized")) {
|
|
197
|
+
reject(new Error(`Command not found: "${cmd}". Ensure it is installed and available in your PATH.`));
|
|
198
|
+
return;
|
|
106
199
|
}
|
|
107
200
|
resolve({
|
|
108
|
-
exitCode:
|
|
109
|
-
stdout
|
|
110
|
-
stderr
|
|
201
|
+
exitCode: code ?? 1,
|
|
202
|
+
stdout,
|
|
203
|
+
stderr,
|
|
111
204
|
});
|
|
112
205
|
});
|
|
113
206
|
// Pipe stdin data if provided, then close the stream so the child
|
package/dist/runner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/sanitize.d.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sanitizes error output by replacing sensitive filesystem paths
|
|
3
|
-
* home-relative equivalents. This prevents leaking usernames and absolute
|
|
4
|
-
* home directory paths in error messages returned to MCP clients.
|
|
2
|
+
* Sanitizes error output by replacing sensitive filesystem paths.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
4
|
+
* Default mode (home paths only):
|
|
7
5
|
* /home/<user>/... → ~/...
|
|
8
6
|
* /Users/<user>/... → ~/...
|
|
9
7
|
* /root/... → ~/...
|
|
10
8
|
* C:\Users\<user>\... → ~\...
|
|
9
|
+
*
|
|
10
|
+
* Broad mode (PARE_SANITIZE_ALL_PATHS=true):
|
|
11
|
+
* Also redacts other absolute paths outside home directories:
|
|
12
|
+
* /etc/..., /var/..., /opt/..., C:\Program Files\..., etc.
|
|
13
|
+
* These are replaced with <redacted-path>/basename to preserve
|
|
14
|
+
* the filename while hiding directory structure.
|
|
11
15
|
*/
|
|
12
16
|
export declare function sanitizeErrorOutput(text: string): string;
|
|
13
17
|
//# sourceMappingURL=sanitize.d.ts.map
|
package/dist/sanitize.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"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"}
|
package/dist/sanitize.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sanitizes error output by replacing sensitive filesystem paths
|
|
3
|
-
* home-relative equivalents. This prevents leaking usernames and absolute
|
|
4
|
-
* home directory paths in error messages returned to MCP clients.
|
|
2
|
+
* Sanitizes error output by replacing sensitive filesystem paths.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
4
|
+
* Default mode (home paths only):
|
|
7
5
|
* /home/<user>/... → ~/...
|
|
8
6
|
* /Users/<user>/... → ~/...
|
|
9
7
|
* /root/... → ~/...
|
|
10
8
|
* C:\Users\<user>\... → ~\...
|
|
9
|
+
*
|
|
10
|
+
* Broad mode (PARE_SANITIZE_ALL_PATHS=true):
|
|
11
|
+
* Also redacts other absolute paths outside home directories:
|
|
12
|
+
* /etc/..., /var/..., /opt/..., C:\Program Files\..., etc.
|
|
13
|
+
* These are replaced with <redacted-path>/basename to preserve
|
|
14
|
+
* the filename while hiding directory structure.
|
|
11
15
|
*/
|
|
12
16
|
export function sanitizeErrorOutput(text) {
|
|
13
17
|
// Unix: /home/<username>/rest → ~/rest
|
|
@@ -18,6 +22,19 @@ export function sanitizeErrorOutput(text) {
|
|
|
18
22
|
result = result.replace(/\/root\//g, "~/");
|
|
19
23
|
// Windows: C:\Users\<username>\rest → ~\rest (with escaped or literal backslashes)
|
|
20
24
|
result = result.replace(/[A-Z]:\\Users\\[^\\:\s]+\\/gi, "~\\");
|
|
25
|
+
// Broad mode: redact remaining absolute paths
|
|
26
|
+
if (process.env.PARE_SANITIZE_ALL_PATHS === "true") {
|
|
27
|
+
// Unix absolute paths not already handled (e.g., /etc/foo/bar → <redacted>/bar)
|
|
28
|
+
result = result.replace(/\/(?:etc|var|opt|usr|tmp|srv|snap|nix)\/[^\s:]+/g, (match) => {
|
|
29
|
+
const basename = match.split("/").pop() ?? "";
|
|
30
|
+
return `<redacted-path>/${basename}`;
|
|
31
|
+
});
|
|
32
|
+
// Windows non-user absolute paths (e.g., C:\Program Files\foo → <redacted>\foo)
|
|
33
|
+
result = result.replace(/[A-Z]:\\(?!Users\\)[^\s:]+/gi, (match) => {
|
|
34
|
+
const basename = match.split("\\").pop() ?? "";
|
|
35
|
+
return `<redacted-path>\\${basename}`;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
21
38
|
return result;
|
|
22
39
|
}
|
|
23
40
|
//# sourceMappingURL=sanitize.js.map
|
package/dist/sanitize.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"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"}
|
package/dist/validation.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validates that a string argument is safe to pass as a positional argument to a CLI tool.
|
|
3
3
|
* Prevents flag injection attacks (e.g., passing "--output=/etc/passwd" as a ref name).
|
|
4
|
-
* See: CVE-2025-68144, CVE-2025-68145
|
|
4
|
+
* 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)
|
|
5
5
|
*/
|
|
6
6
|
export declare function assertNoFlagInjection(value: string, paramName: string): void;
|
|
7
7
|
/**
|
package/dist/validation.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validates that a string argument is safe to pass as a positional argument to a CLI tool.
|
|
3
3
|
* Prevents flag injection attacks (e.g., passing "--output=/etc/passwd" as a ref name).
|
|
4
|
-
* See: CVE-2025-68144, CVE-2025-68145
|
|
4
|
+
* 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)
|
|
5
5
|
*/
|
|
6
6
|
export function assertNoFlagInjection(value, paramName) {
|
|
7
7
|
if (value.trimStart().startsWith("-")) {
|