@vinhnt-sdk/sandbox 0.1.3
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/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/backends/host.d.ts +4 -0
- package/dist/backends/host.d.ts.map +1 -0
- package/dist/backends/host.js +57 -0
- package/dist/backends/host.js.map +1 -0
- package/dist/backends/process.d.ts +4 -0
- package/dist/backends/process.d.ts.map +1 -0
- package/dist/backends/process.js +150 -0
- package/dist/backends/process.js.map +1 -0
- package/dist/error.d.ts +16 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +24 -0
- package/dist/error.js.map +1 -0
- package/dist/factory.d.ts +20 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +21 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/kill-tree.d.ts +69 -0
- package/dist/kill-tree.d.ts.map +1 -0
- package/dist/kill-tree.js +138 -0
- package/dist/kill-tree.js.map +1 -0
- package/dist/shell-parser.d.ts +9 -0
- package/dist/shell-parser.d.ts.map +1 -0
- package/dist/shell-parser.js +69 -0
- package/dist/shell-parser.js.map +1 -0
- package/dist/timeout.d.ts +31 -0
- package/dist/timeout.d.ts.map +1 -0
- package/dist/timeout.js +57 -0
- package/dist/timeout.js.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nguyen Thanh Vinh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @vinhnt-sdk/sandbox
|
|
2
|
+
|
|
3
|
+
Sandbox primitives + fail-closed factory for isolated tool/command execution.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { createSandbox, SandboxUnavailableError } from "@vinhnt-sdk/sandbox";
|
|
7
|
+
import { createHostSandbox } from "@vinhnt-sdk/sandbox-host";
|
|
8
|
+
import { createProcessSandbox } from "@vinhnt-sdk/sandbox-process";
|
|
9
|
+
|
|
10
|
+
const sandbox = createSandbox({ defaultTimeoutMs: 30_000, scope: "process" }, {
|
|
11
|
+
host: createHostSandbox,
|
|
12
|
+
process: createProcessSandbox,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const result = await sandbox.execute({
|
|
16
|
+
command: "node ./script.js",
|
|
17
|
+
cwd: "/workspace",
|
|
18
|
+
timeoutMs: 30_000,
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Fail-closed by default
|
|
23
|
+
|
|
24
|
+
Requesting a sandbox scope that has no registered backend throws
|
|
25
|
+
`SandboxUnavailableError` — it never silently downgrades to a weaker sandbox.
|
|
26
|
+
Wire only the backends your application actually supports.
|
|
27
|
+
|
|
28
|
+
## Contents
|
|
29
|
+
|
|
30
|
+
- `ProcessSandbox` interface — `execute()` / `destroy()` contract.
|
|
31
|
+
- `SandboxConfig`, `SandboxResult`, `SandboxScope`, `KNOWN_SANDBOX_SCOPES`.
|
|
32
|
+
- `SandboxUnavailableError` — fail-closed error carrying the missing scope.
|
|
33
|
+
- `createSandbox(config, backends)` — fail-closed factory.
|
|
34
|
+
- `parseCommand` — shell command tokenizer.
|
|
35
|
+
- `killProcessTree` / `isPidAlive` / `treeKillSpawnOptions` — tree-scoped termination.
|
|
36
|
+
- Backend packages: `@vinhnt-sdk/sandbox-host`, `@vinhnt-sdk/sandbox-process`, `@vinhnt-sdk/sandbox-container`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/backends/host.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAiB,cAAc,EAAgC,MAAM,aAAa,CAAC;AA8D9G,oDAAoD;AACpD,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,cAAc,CAEzE"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { parseCommand } from "../shell-parser.js";
|
|
3
|
+
import { treeKillSpawnOptions } from "../kill-tree.js";
|
|
4
|
+
import { withTimeoutAndAbort } from "../timeout.js";
|
|
5
|
+
import { sanitizeEnv } from "@vinhnt-sdk/security";
|
|
6
|
+
class HostSandbox {
|
|
7
|
+
scope = "host";
|
|
8
|
+
async execute(options) {
|
|
9
|
+
const start = Date.now();
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
const parsed = parseCommand(options.command);
|
|
12
|
+
const child = execFile(parsed.file, parsed.args, {
|
|
13
|
+
cwd: options.cwd,
|
|
14
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
15
|
+
encoding: "utf-8",
|
|
16
|
+
windowsHide: true,
|
|
17
|
+
...treeKillSpawnOptions(),
|
|
18
|
+
env: options.env && Object.keys(options.env).length > 0
|
|
19
|
+
? { ...sanitizeEnv(), ...options.env }
|
|
20
|
+
: sanitizeEnv(),
|
|
21
|
+
}, (err, stdout, stderr) => {
|
|
22
|
+
const durationMs = Date.now() - start;
|
|
23
|
+
if (err) {
|
|
24
|
+
const exitCode = typeof err.code === "number" ? err.code : 1;
|
|
25
|
+
resolve({
|
|
26
|
+
result: { stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), exitCode },
|
|
27
|
+
exitCode,
|
|
28
|
+
durationMs,
|
|
29
|
+
timedOut: handle.timedOut,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
resolve({
|
|
34
|
+
result: { stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), exitCode: 0 },
|
|
35
|
+
exitCode: 0,
|
|
36
|
+
durationMs,
|
|
37
|
+
timedOut: false,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
const handle = withTimeoutAndAbort(child, options.timeoutMs, options.signal, (reason) => {
|
|
42
|
+
resolve({
|
|
43
|
+
result: { stdout: "", stderr: reason, exitCode: 1 },
|
|
44
|
+
exitCode: 1,
|
|
45
|
+
durationMs: Date.now() - start,
|
|
46
|
+
timedOut: handle.timedOut,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async destroy() { }
|
|
52
|
+
}
|
|
53
|
+
/** Create a host (no-isolation) sandbox backend. */
|
|
54
|
+
export function createHostSandbox(_config) {
|
|
55
|
+
return new HostSandbox();
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/backends/host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/backends/process.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAiB,cAAc,EAAgC,MAAM,aAAa,CAAC;AAyJ9G,kDAAkD;AAClD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,GAAG,cAAc,CAE1E"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import { relative, isAbsolute } from "node:path";
|
|
4
|
+
import { parseCommand } from "../shell-parser.js";
|
|
5
|
+
import { treeKillSpawnOptions } from "../kill-tree.js";
|
|
6
|
+
import { withTimeoutAndAbort } from "../timeout.js";
|
|
7
|
+
const DEFAULT_ALLOWED_COMMANDS = new Set([
|
|
8
|
+
"node", "npm", "npx", "pnpm", "git",
|
|
9
|
+
"ls", "cat", "grep", "find", "head", "tail", "wc", "sort", "uniq", "diff",
|
|
10
|
+
"echo", "pwd", "whoami", "date",
|
|
11
|
+
]);
|
|
12
|
+
const BLOCKED_COMMAND_PATTERNS = [
|
|
13
|
+
/\brm\s+-rf\s+\/(?:[^a-z]|$)/i,
|
|
14
|
+
/\bmkfs\b/i,
|
|
15
|
+
/\bdd\b.*of=\/dev/i,
|
|
16
|
+
/\bformat\b/i,
|
|
17
|
+
/\bshutdown\b/i,
|
|
18
|
+
/\breboot\b/i,
|
|
19
|
+
/\bkill\s+-9\s+1\b/i,
|
|
20
|
+
/\bcurl\b.*\|\s*sh/i,
|
|
21
|
+
/\bwget\b.*\|\s*sh/i,
|
|
22
|
+
];
|
|
23
|
+
function extractBaseName(file) {
|
|
24
|
+
const name = file.split("/").pop()?.split("\\").pop() ?? file;
|
|
25
|
+
return process.platform === "win32" ? name.toLowerCase().replace(/\.exe$/, "") : name;
|
|
26
|
+
}
|
|
27
|
+
function isCommandAllowed(command, allowedCommands) {
|
|
28
|
+
const parsed = parseCommand(command);
|
|
29
|
+
const baseCommand = extractBaseName(parsed.file);
|
|
30
|
+
for (const pattern of BLOCKED_COMMAND_PATTERNS) {
|
|
31
|
+
if (pattern.test(command))
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const allowlist = allowedCommands ?? DEFAULT_ALLOWED_COMMANDS;
|
|
35
|
+
return allowlist.has(baseCommand);
|
|
36
|
+
}
|
|
37
|
+
function isPathWithin(target, roots) {
|
|
38
|
+
for (const root of roots) {
|
|
39
|
+
const rel = relative(root, target);
|
|
40
|
+
if (rel === "")
|
|
41
|
+
return true;
|
|
42
|
+
if (rel.startsWith(".."))
|
|
43
|
+
continue;
|
|
44
|
+
if (isAbsolute(rel))
|
|
45
|
+
continue;
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function createEmptyEnv(allowedVars) {
|
|
51
|
+
const env = {};
|
|
52
|
+
if (allowedVars) {
|
|
53
|
+
for (const key of allowedVars) {
|
|
54
|
+
const value = process.env[key];
|
|
55
|
+
if (value !== undefined)
|
|
56
|
+
env[key] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return env;
|
|
60
|
+
}
|
|
61
|
+
class ProcessSandboxImpl {
|
|
62
|
+
scope = "process";
|
|
63
|
+
config;
|
|
64
|
+
constructor(config) {
|
|
65
|
+
this.config = config;
|
|
66
|
+
}
|
|
67
|
+
async execute(options) {
|
|
68
|
+
const start = Date.now();
|
|
69
|
+
if (!isCommandAllowed(options.command, this.config.allowedCommands)) {
|
|
70
|
+
return {
|
|
71
|
+
result: { stdout: "", stderr: `Command not allowed: ${options.command.split(" ")[0]}`, exitCode: 126 },
|
|
72
|
+
exitCode: 126, durationMs: Date.now() - start, timedOut: false,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (this.config.allowedPaths && this.config.allowedPaths.length > 0) {
|
|
76
|
+
let cwdReal;
|
|
77
|
+
const allowedReal = [];
|
|
78
|
+
try {
|
|
79
|
+
cwdReal = await realpath(options.cwd);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
cwdReal = options.cwd;
|
|
83
|
+
}
|
|
84
|
+
for (const p of this.config.allowedPaths) {
|
|
85
|
+
let rootReal = p;
|
|
86
|
+
try {
|
|
87
|
+
rootReal = await realpath(p);
|
|
88
|
+
}
|
|
89
|
+
catch { /* fall back to lexical path */ }
|
|
90
|
+
allowedReal.push(rootReal);
|
|
91
|
+
}
|
|
92
|
+
if (!isPathWithin(cwdReal, allowedReal)) {
|
|
93
|
+
return {
|
|
94
|
+
result: { stdout: "", stderr: `Working directory outside allowed paths: ${options.cwd}`, exitCode: 126 },
|
|
95
|
+
exitCode: 126, durationMs: Date.now() - start, timedOut: false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const secureEnv = this.config.allowedEnvVars
|
|
100
|
+
? createEmptyEnv(this.config.allowedEnvVars)
|
|
101
|
+
: createEmptyEnv();
|
|
102
|
+
const parsed = parseCommand(options.command);
|
|
103
|
+
const execArgs = [];
|
|
104
|
+
if (this.config.enablePermissionModel && extractBaseName(parsed.file) === "node") {
|
|
105
|
+
execArgs.push("--permission");
|
|
106
|
+
execArgs.push(`--allow-fs-read=${options.cwd}`);
|
|
107
|
+
execArgs.push("--allow-fs-read=/tmp");
|
|
108
|
+
execArgs.push("--allow-fs-write=/tmp");
|
|
109
|
+
}
|
|
110
|
+
const maxBuffer = 10 * 1024 * 1024;
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const child = execFile(parsed.file, [...execArgs, ...parsed.args], {
|
|
113
|
+
cwd: options.cwd,
|
|
114
|
+
maxBuffer,
|
|
115
|
+
encoding: "utf-8",
|
|
116
|
+
windowsHide: true,
|
|
117
|
+
...treeKillSpawnOptions(),
|
|
118
|
+
env: secureEnv,
|
|
119
|
+
...(process.platform !== "win32" ? { uid: process.getuid?.(), gid: process.getgid?.() } : {}),
|
|
120
|
+
}, (err, stdout, stderr) => {
|
|
121
|
+
const durationMs = Date.now() - start;
|
|
122
|
+
if (err) {
|
|
123
|
+
const exitCode = typeof err.code === "number" ? err.code : 1;
|
|
124
|
+
resolve({
|
|
125
|
+
result: { stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), exitCode },
|
|
126
|
+
exitCode, durationMs, timedOut: handle.timedOut,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
resolve({
|
|
131
|
+
result: { stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), exitCode: 0 },
|
|
132
|
+
exitCode: 0, durationMs, timedOut: false,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
const handle = withTimeoutAndAbort(child, options.timeoutMs, options.signal, (reason) => {
|
|
137
|
+
resolve({
|
|
138
|
+
result: { stdout: "", stderr: reason, exitCode: 1 },
|
|
139
|
+
exitCode: 1, durationMs: Date.now() - start, timedOut: handle.timedOut,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async destroy() { }
|
|
145
|
+
}
|
|
146
|
+
/** Create a process-isolation sandbox backend. */
|
|
147
|
+
export function createProcessSandbox(config) {
|
|
148
|
+
return new ProcessSandboxImpl(config);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/backends/process.ts"],"names":[],"mappings":"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"}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { VntError } from "@vinhnt-sdk/schema";
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a sandbox scope is requested but no backend is available for it.
|
|
4
|
+
*
|
|
5
|
+
* Sandboxes are **fail-closed**: requesting an unsupported/unavailable scope
|
|
6
|
+
* never silently downgrades to a weaker sandbox. Instead this error makes the
|
|
7
|
+
* gap explicit, listing the scopes that ARE wired.
|
|
8
|
+
*/
|
|
9
|
+
export declare class SandboxUnavailableError extends VntError {
|
|
10
|
+
readonly scope: string;
|
|
11
|
+
readonly availableScopes: readonly string[];
|
|
12
|
+
readonly code: "ERR_SANDBOX_UNAVAILABLE";
|
|
13
|
+
readonly retryable = false;
|
|
14
|
+
constructor(scope: string, availableScopes?: readonly string[]);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C;;;;;;GAMG;AACH,qBAAa,uBAAwB,SAAQ,QAAQ;aAKjC,KAAK,EAAE,MAAM;aACb,eAAe,EAAE,SAAS,MAAM,EAAE;IALpD,SAAyB,IAAI,EAAG,yBAAyB,CAAU;IACnE,SAAyB,SAAS,SAAS;gBAGzB,KAAK,EAAE,MAAM,EACb,eAAe,GAAE,SAAS,MAAM,EAAO;CAU1D"}
|
package/dist/error.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { VntError } from "@vinhnt-sdk/schema";
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a sandbox scope is requested but no backend is available for it.
|
|
4
|
+
*
|
|
5
|
+
* Sandboxes are **fail-closed**: requesting an unsupported/unavailable scope
|
|
6
|
+
* never silently downgrades to a weaker sandbox. Instead this error makes the
|
|
7
|
+
* gap explicit, listing the scopes that ARE wired.
|
|
8
|
+
*/
|
|
9
|
+
export class SandboxUnavailableError extends VntError {
|
|
10
|
+
scope;
|
|
11
|
+
availableScopes;
|
|
12
|
+
code = "ERR_SANDBOX_UNAVAILABLE";
|
|
13
|
+
retryable = false;
|
|
14
|
+
constructor(scope, availableScopes = []) {
|
|
15
|
+
super(`Sandbox scope "${scope}" is not available` +
|
|
16
|
+
(availableScopes.length > 0
|
|
17
|
+
? ` (available: ${availableScopes.join(", ")})`
|
|
18
|
+
: " — no sandbox backends registered; wire a backend package like @vinhnt-sdk/sandbox-host or @vinhnt-sdk/sandbox-process"));
|
|
19
|
+
this.scope = scope;
|
|
20
|
+
this.availableScopes = availableScopes;
|
|
21
|
+
this.name = "SandboxUnavailableError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,OAAO,uBAAwB,SAAQ,QAAQ;IAKjC;IACA;IALO,IAAI,GAAG,yBAAkC,CAAC;IAC1C,SAAS,GAAG,KAAK,CAAC;IAE3C,YACkB,KAAa,EACb,kBAAqC,EAAE;QAEvD,KAAK,CACH,kBAAkB,KAAK,oBAAoB;YAC3C,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC;gBACzB,CAAC,CAAC,gBAAgB,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;gBAC/C,CAAC,CAAC,wHAAwH,CAAC,CAC9H,CAAC;QARc,UAAK,GAAL,KAAK,CAAQ;QACb,oBAAe,GAAf,eAAe,CAAwB;QAQvD,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SandboxConfig, ProcessSandbox, SandboxScope } from "./types.js";
|
|
2
|
+
/** Factory that builds a `ProcessSandbox` for a scope from a config. */
|
|
3
|
+
export type SandboxBackendFactory = (config: SandboxConfig) => ProcessSandbox;
|
|
4
|
+
/**
|
|
5
|
+
* Backend registry passed to `createSandbox`. Keys are sandbox scopes; values
|
|
6
|
+
* are factories (or undefined if the scope is not wired).
|
|
7
|
+
*/
|
|
8
|
+
export type SandboxBackends = Record<SandboxScope, SandboxBackendFactory | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* Build a sandbox for the configured scope.
|
|
11
|
+
*
|
|
12
|
+
* **Fail-closed**: if the requested scope has no registered backend this throws
|
|
13
|
+
* `SandboxUnavailableError` — it never silently downgrades to a weaker sandbox.
|
|
14
|
+
*
|
|
15
|
+
* @param config - Sandbox configuration.
|
|
16
|
+
* @param backends - Map of scope → backend factory. Wire only what you support.
|
|
17
|
+
* @returns ProcessSandbox instance for the requested scope.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createSandbox(config: SandboxConfig, backends?: SandboxBackends): ProcessSandbox;
|
|
20
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG9E,wEAAwE;AACxE,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,aAAa,KAAK,cAAc,CAAC;AAE9E;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,YAAY,EAAE,qBAAqB,GAAG,SAAS,CAAC,CAAC;AAEtF;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,GAAE,eAAoB,GAAG,cAAc,CAQnG"}
|
package/dist/factory.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SandboxUnavailableError } from "./error.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a sandbox for the configured scope.
|
|
4
|
+
*
|
|
5
|
+
* **Fail-closed**: if the requested scope has no registered backend this throws
|
|
6
|
+
* `SandboxUnavailableError` — it never silently downgrades to a weaker sandbox.
|
|
7
|
+
*
|
|
8
|
+
* @param config - Sandbox configuration.
|
|
9
|
+
* @param backends - Map of scope → backend factory. Wire only what you support.
|
|
10
|
+
* @returns ProcessSandbox instance for the requested scope.
|
|
11
|
+
*/
|
|
12
|
+
export function createSandbox(config, backends = {}) {
|
|
13
|
+
const scope = config.scope ?? "host";
|
|
14
|
+
const factory = backends[scope];
|
|
15
|
+
if (!factory) {
|
|
16
|
+
const availableScopes = Object.keys(backends).filter((s) => backends[s] !== undefined);
|
|
17
|
+
throw new SandboxUnavailableError(scope, availableScopes);
|
|
18
|
+
}
|
|
19
|
+
return factory(config);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAWrD;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,MAAqB,EAAE,WAA4B,EAAE;IACjF,MAAM,KAAK,GAAiB,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC;IACnD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;QACvF,MAAM,IAAI,uBAAuB,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AACzB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { SandboxScope, SandboxConfig, SandboxResult, ProcessSandbox, ProcessSandboxExecuteOptions } from "./types.js";
|
|
2
|
+
export { KNOWN_SANDBOX_SCOPES } from "./types.js";
|
|
3
|
+
export { SandboxUnavailableError } from "./error.js";
|
|
4
|
+
export { createSandbox } from "./factory.js";
|
|
5
|
+
export type { SandboxBackendFactory, SandboxBackends } from "./factory.js";
|
|
6
|
+
export { parseCommand } from "./shell-parser.js";
|
|
7
|
+
export { killProcessTree, killProcessTreeAndWait, killTreeTrackedPids, isPidAlive, pruneKillTreeState, resetKillTreeState, treeKillSpawnOptions } from "./kill-tree.js";
|
|
8
|
+
export { withTimeoutAndAbort } from "./timeout.js";
|
|
9
|
+
export { createHostSandbox } from "./backends/host.js";
|
|
10
|
+
export { createProcessSandbox } from "./backends/process.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAC3H,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,UAAU,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACxK,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAInD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { KNOWN_SANDBOX_SCOPES } from "./types.js";
|
|
2
|
+
export { SandboxUnavailableError } from "./error.js";
|
|
3
|
+
export { createSandbox } from "./factory.js";
|
|
4
|
+
export { parseCommand } from "./shell-parser.js";
|
|
5
|
+
export { killProcessTree, killProcessTreeAndWait, killTreeTrackedPids, isPidAlive, pruneKillTreeState, resetKillTreeState, treeKillSpawnOptions } from "./kill-tree.js";
|
|
6
|
+
export { withTimeoutAndAbort } from "./timeout.js";
|
|
7
|
+
// Re-export backend factories for backward compatibility
|
|
8
|
+
// These were previously in separate packages (sandbox-host, sandbox-process)
|
|
9
|
+
export { createHostSandbox } from "./backends/host.js";
|
|
10
|
+
export { createProcessSandbox } from "./backends/process.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,UAAU,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACxK,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,yDAAyD;AACzD,6EAA6E;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-scoped process termination.
|
|
3
|
+
*
|
|
4
|
+
* `child.kill()` only terminates the direct child; grandchild processes keep
|
|
5
|
+
* running (orphaned). This module kills the whole process tree:
|
|
6
|
+
* - POSIX: child is spawned `detached` (own process group) → `kill(-pid)`
|
|
7
|
+
* - Windows: `taskkill /pid <pid> /T /F`
|
|
8
|
+
*
|
|
9
|
+
* Termination is idempotent: repeated kills for the same pid are no-ops while
|
|
10
|
+
* the kill is still in-flight. The registry is pruned once the tracked process
|
|
11
|
+
* exits (or its TTL elapses) so an OS pid reuse is not silently swallowed.
|
|
12
|
+
*
|
|
13
|
+
* @module sandbox/kill-tree
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
import { type ChildProcess } from "node:child_process";
|
|
17
|
+
/** Clear the idempotency registry (used by tests). */
|
|
18
|
+
export declare function resetKillTreeState(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Pids currently tracked as "kill already issued".
|
|
21
|
+
*
|
|
22
|
+
* Exposed for observability/tests so a caller can confirm that a terminated
|
|
23
|
+
* pid is no longer held (i.e. it is safe to kill again after reuse).
|
|
24
|
+
*/
|
|
25
|
+
export declare function killTreeTrackedPids(): number[];
|
|
26
|
+
/**
|
|
27
|
+
* Prune the idempotency registry: drop pids whose process has exited or whose
|
|
28
|
+
* kill is older than the TTL. Without pruning the registry grows forever and a
|
|
29
|
+
* reused pid is treated as "already killed" — a silent no-op (RV-11).
|
|
30
|
+
*
|
|
31
|
+
* @param now - Clock value to compare the TTL against (injectable for tests).
|
|
32
|
+
*/
|
|
33
|
+
export declare function pruneKillTreeState(now?: number): void;
|
|
34
|
+
/** True when the process (or its process group) is still alive. */
|
|
35
|
+
export declare function isPidAlive(pid: number): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Kill a child process and its whole subtree. Idempotent per pid while the
|
|
38
|
+
* kill is in-flight (the registry is pruned once the process exits).
|
|
39
|
+
*
|
|
40
|
+
* @param child - The spawned child process.
|
|
41
|
+
* @param signal - POSIX signal for the direct kill (default "SIGTERM").
|
|
42
|
+
* Windows always uses `taskkill /T /F`.
|
|
43
|
+
* @returns `true` when the kill was issued (or already issued), `false` when
|
|
44
|
+
* there is no pid yet (child not spawned).
|
|
45
|
+
*/
|
|
46
|
+
export declare function killProcessTree(child: ChildProcess, signal?: NodeJS.Signals): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Kill a child process and its whole subtree, then await its exit.
|
|
49
|
+
*
|
|
50
|
+
* This is the awaited form of {@link killProcessTree} (RV-11): on Windows the
|
|
51
|
+
* `taskkill /T` spawn is no longer fire-and-forget — the returned promise
|
|
52
|
+
* resolves only after the child has actually exited, or the given timeout
|
|
53
|
+
* elapses.
|
|
54
|
+
*
|
|
55
|
+
* @param child - The spawned child process.
|
|
56
|
+
* @param signal - POSIX signal for the direct kill (default "SIGTERM").
|
|
57
|
+
* @param timeoutMs - How long to wait for the child to exit (default 5000).
|
|
58
|
+
* @returns `true` when the kill was issued (or already issued), `false` when
|
|
59
|
+
* there is no pid yet (child not spawned).
|
|
60
|
+
*/
|
|
61
|
+
export declare function killProcessTreeAndWait(child: ChildProcess, signal?: NodeJS.Signals, timeoutMs?: number): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Cross-platform spawn option that makes `killProcessTree` reach the whole
|
|
64
|
+
* tree. On Windows no extra flag is needed (taskkill walks the tree).
|
|
65
|
+
*/
|
|
66
|
+
export declare function treeKillSpawnOptions(): {
|
|
67
|
+
detached: boolean;
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=kill-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kill-tree.d.ts","sourceRoot":"","sources":["../src/kill-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAK9D,sDAAsD;AACtD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAmB,GAAG,IAAI,CAMjE;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQ/C;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,GAAE,MAAM,CAAC,OAAmB,GAAG,OAAO,CA8BhG;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,YAAY,EACnB,MAAM,GAAE,MAAM,CAAC,OAAmB,EAClC,SAAS,SAAQ,GAChB,OAAO,CAAC,OAAO,CAAC,CAclB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAE5D"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-scoped process termination.
|
|
3
|
+
*
|
|
4
|
+
* `child.kill()` only terminates the direct child; grandchild processes keep
|
|
5
|
+
* running (orphaned). This module kills the whole process tree:
|
|
6
|
+
* - POSIX: child is spawned `detached` (own process group) → `kill(-pid)`
|
|
7
|
+
* - Windows: `taskkill /pid <pid> /T /F`
|
|
8
|
+
*
|
|
9
|
+
* Termination is idempotent: repeated kills for the same pid are no-ops while
|
|
10
|
+
* the kill is still in-flight. The registry is pruned once the tracked process
|
|
11
|
+
* exits (or its TTL elapses) so an OS pid reuse is not silently swallowed.
|
|
12
|
+
*
|
|
13
|
+
* @module sandbox/kill-tree
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
const terminatedPids = new Map();
|
|
18
|
+
const KILL_TTL_MS = 30_000;
|
|
19
|
+
/** Clear the idempotency registry (used by tests). */
|
|
20
|
+
export function resetKillTreeState() {
|
|
21
|
+
terminatedPids.clear();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Pids currently tracked as "kill already issued".
|
|
25
|
+
*
|
|
26
|
+
* Exposed for observability/tests so a caller can confirm that a terminated
|
|
27
|
+
* pid is no longer held (i.e. it is safe to kill again after reuse).
|
|
28
|
+
*/
|
|
29
|
+
export function killTreeTrackedPids() {
|
|
30
|
+
return [...terminatedPids.keys()];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Prune the idempotency registry: drop pids whose process has exited or whose
|
|
34
|
+
* kill is older than the TTL. Without pruning the registry grows forever and a
|
|
35
|
+
* reused pid is treated as "already killed" — a silent no-op (RV-11).
|
|
36
|
+
*
|
|
37
|
+
* @param now - Clock value to compare the TTL against (injectable for tests).
|
|
38
|
+
*/
|
|
39
|
+
export function pruneKillTreeState(now = Date.now()) {
|
|
40
|
+
for (const [pid, killedAt] of terminatedPids) {
|
|
41
|
+
if (!isPidAlive(pid) || now - killedAt > KILL_TTL_MS) {
|
|
42
|
+
terminatedPids.delete(pid);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** True when the process (or its process group) is still alive. */
|
|
47
|
+
export function isPidAlive(pid) {
|
|
48
|
+
if (!pid || pid <= 0)
|
|
49
|
+
return false;
|
|
50
|
+
try {
|
|
51
|
+
process.kill(pid, 0);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
return err.code !== "ESRCH";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Kill a child process and its whole subtree. Idempotent per pid while the
|
|
60
|
+
* kill is in-flight (the registry is pruned once the process exits).
|
|
61
|
+
*
|
|
62
|
+
* @param child - The spawned child process.
|
|
63
|
+
* @param signal - POSIX signal for the direct kill (default "SIGTERM").
|
|
64
|
+
* Windows always uses `taskkill /T /F`.
|
|
65
|
+
* @returns `true` when the kill was issued (or already issued), `false` when
|
|
66
|
+
* there is no pid yet (child not spawned).
|
|
67
|
+
*/
|
|
68
|
+
export function killProcessTree(child, signal = "SIGTERM") {
|
|
69
|
+
const pid = child.pid;
|
|
70
|
+
if (!pid || pid <= 0)
|
|
71
|
+
return false;
|
|
72
|
+
pruneKillTreeState();
|
|
73
|
+
if (terminatedPids.has(pid))
|
|
74
|
+
return true;
|
|
75
|
+
terminatedPids.set(pid, Date.now());
|
|
76
|
+
if (process.platform === "win32") {
|
|
77
|
+
spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
|
|
78
|
+
windowsHide: true,
|
|
79
|
+
stdio: "ignore",
|
|
80
|
+
}).unref();
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
// POSIX: the child was spawned detached → kill the whole process group.
|
|
84
|
+
try {
|
|
85
|
+
process.kill(-pid, signal);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
if (err.code === "ESRCH") {
|
|
89
|
+
// Already gone — group kill is best-effort.
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
process.kill(pid, signal);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Already gone.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Kill a child process and its whole subtree, then await its exit.
|
|
103
|
+
*
|
|
104
|
+
* This is the awaited form of {@link killProcessTree} (RV-11): on Windows the
|
|
105
|
+
* `taskkill /T` spawn is no longer fire-and-forget — the returned promise
|
|
106
|
+
* resolves only after the child has actually exited, or the given timeout
|
|
107
|
+
* elapses.
|
|
108
|
+
*
|
|
109
|
+
* @param child - The spawned child process.
|
|
110
|
+
* @param signal - POSIX signal for the direct kill (default "SIGTERM").
|
|
111
|
+
* @param timeoutMs - How long to wait for the child to exit (default 5000).
|
|
112
|
+
* @returns `true` when the kill was issued (or already issued), `false` when
|
|
113
|
+
* there is no pid yet (child not spawned).
|
|
114
|
+
*/
|
|
115
|
+
export function killProcessTreeAndWait(child, signal = "SIGTERM", timeoutMs = 5_000) {
|
|
116
|
+
const issued = killProcessTree(child, signal);
|
|
117
|
+
if (!issued)
|
|
118
|
+
return Promise.resolve(false);
|
|
119
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
120
|
+
return Promise.resolve(true);
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
const timer = setTimeout(() => resolve(true), timeoutMs);
|
|
123
|
+
const done = () => {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
resolve(true);
|
|
126
|
+
};
|
|
127
|
+
child.once("exit", done);
|
|
128
|
+
child.once("error", done);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Cross-platform spawn option that makes `killProcessTree` reach the whole
|
|
133
|
+
* tree. On Windows no extra flag is needed (taskkill walks the tree).
|
|
134
|
+
*/
|
|
135
|
+
export function treeKillSpawnOptions() {
|
|
136
|
+
return { detached: process.platform !== "win32" };
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=kill-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kill-tree.js","sourceRoot":"","sources":["../src/kill-tree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-parser.d.ts","sourceRoot":"","sources":["../src/shell-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAiC1E"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell command parsing utility.
|
|
3
|
+
* Extracts file and args from a command string, handling quotes and escapes.
|
|
4
|
+
*/
|
|
5
|
+
export function parseCommand(cmd) {
|
|
6
|
+
const trimmed = cmd.trim();
|
|
7
|
+
if (!trimmed)
|
|
8
|
+
return { file: "", args: [] };
|
|
9
|
+
const tokens = [];
|
|
10
|
+
let i = 0;
|
|
11
|
+
let current = "";
|
|
12
|
+
let inSingle = false;
|
|
13
|
+
let inDouble = false;
|
|
14
|
+
while (i < trimmed.length) {
|
|
15
|
+
const ch = trimmed[i] ?? "";
|
|
16
|
+
if (inSingle) {
|
|
17
|
+
if (ch === "'") {
|
|
18
|
+
inSingle = false;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
current += ch;
|
|
22
|
+
}
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
else if (inDouble) {
|
|
26
|
+
if (ch === '"') {
|
|
27
|
+
inDouble = false;
|
|
28
|
+
i++;
|
|
29
|
+
}
|
|
30
|
+
else if (ch === "\\" && i + 1 < trimmed.length) {
|
|
31
|
+
current += trimmed[i + 1];
|
|
32
|
+
i += 2;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
current += ch;
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (ch === "'") {
|
|
40
|
+
inSingle = true;
|
|
41
|
+
i++;
|
|
42
|
+
}
|
|
43
|
+
else if (ch === '"') {
|
|
44
|
+
inDouble = true;
|
|
45
|
+
i++;
|
|
46
|
+
}
|
|
47
|
+
else if (ch === "\\" && i + 1 < trimmed.length) {
|
|
48
|
+
current += trimmed[i + 1];
|
|
49
|
+
i += 2;
|
|
50
|
+
}
|
|
51
|
+
else if (/\s/.test(ch)) {
|
|
52
|
+
if (current) {
|
|
53
|
+
tokens.push(current);
|
|
54
|
+
current = "";
|
|
55
|
+
}
|
|
56
|
+
i++;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
current += ch;
|
|
60
|
+
i++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (current)
|
|
64
|
+
tokens.push(current);
|
|
65
|
+
if (tokens.length === 0)
|
|
66
|
+
return { file: "", args: [] };
|
|
67
|
+
return { file: tokens[0], args: tokens.slice(1) };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=shell-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-parser.js","sourceRoot":"","sources":["../src/shell-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAE5C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBAAC,QAAQ,GAAG,KAAK,CAAC;YAAC,CAAC;iBAChC,CAAC;gBAAC,OAAO,IAAI,EAAE,CAAC;YAAC,CAAC;YACvB,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,QAAQ,EAAE,CAAC;YACpB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBAAC,QAAQ,GAAG,KAAK,CAAC;gBAAC,CAAC,EAAE,CAAC;YAAC,CAAC;iBACrC,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;gBAAC,OAAO,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAC,CAAC,IAAI,CAAC,CAAC;YAAC,CAAC;iBACjF,CAAC;gBAAC,OAAO,IAAI,EAAE,CAAC;gBAAC,CAAC,EAAE,CAAC;YAAC,CAAC;QAC9B,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YAAC,QAAQ,GAAG,IAAI,CAAC;YAAC,CAAC,EAAE,CAAC;QAAC,CAAC;aAC3C,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YAAC,QAAQ,GAAG,IAAI,CAAC;YAAC,CAAC,EAAE,CAAC;QAAC,CAAC;aACzC,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YAAC,OAAO,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAAC,CAAC,IAAI,CAAC,CAAC;QAAC,CAAC;aACjF,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACvB,IAAI,OAAO,EAAE,CAAC;gBAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;YAAC,CAAC;YACpD,CAAC,EAAE,CAAC;QACN,CAAC;aACI,CAAC;YAAC,OAAO,IAAI,EAAE,CAAC;YAAC,CAAC,EAAE,CAAC;QAAC,CAAC;IAC9B,CAAC;IACD,IAAI,OAAO;QAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAElC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACvD,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ChildProcess } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Attach timeout + abort handling to a child process.
|
|
4
|
+
*
|
|
5
|
+
* This is the shared pattern used by all sandbox backends (host, process,
|
|
6
|
+
* container) to enforce execution timeouts and abort signals. It replaces
|
|
7
|
+
* the duplicated `setTimeout` + `killProcessTree` + abort listener logic
|
|
8
|
+
* that was previously copy-pasted across backends.
|
|
9
|
+
*
|
|
10
|
+
* RV-11: the timeout must kill the whole tree, not just the direct child.
|
|
11
|
+
* `execFile`'s built-in `timeout` option only SIGTERMs the spawned pid,
|
|
12
|
+
* orphaning grandchildren — so the timeout is handled here instead.
|
|
13
|
+
*
|
|
14
|
+
* @param child - The spawned child process
|
|
15
|
+
* @param timeoutMs - Maximum execution time in milliseconds
|
|
16
|
+
* @param signal - Optional abort signal
|
|
17
|
+
* @param onAbort - Callback when timeout or abort fires (resolves the promise)
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const child = execFile(file, args, options, callback);
|
|
22
|
+
* withTimeoutAndAbort(child, 30_000, signal, () => {
|
|
23
|
+
* resolve({ result: { stdout: "", stderr: "Aborted", exitCode: 1 }, exitCode: 1, durationMs: Date.now() - start, timedOut: false });
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function withTimeoutAndAbort(child: ChildProcess, timeoutMs: number, signal: AbortSignal | undefined, onAbort: (reason: string) => void): {
|
|
28
|
+
timedOut: boolean;
|
|
29
|
+
clearTimer: () => void;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=timeout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout.d.ts","sourceRoot":"","sources":["../src/timeout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAChC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,IAAI,CAAA;CAAE,CAgC/C"}
|
package/dist/timeout.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { killProcessTree } from "./kill-tree.js";
|
|
2
|
+
/**
|
|
3
|
+
* Attach timeout + abort handling to a child process.
|
|
4
|
+
*
|
|
5
|
+
* This is the shared pattern used by all sandbox backends (host, process,
|
|
6
|
+
* container) to enforce execution timeouts and abort signals. It replaces
|
|
7
|
+
* the duplicated `setTimeout` + `killProcessTree` + abort listener logic
|
|
8
|
+
* that was previously copy-pasted across backends.
|
|
9
|
+
*
|
|
10
|
+
* RV-11: the timeout must kill the whole tree, not just the direct child.
|
|
11
|
+
* `execFile`'s built-in `timeout` option only SIGTERMs the spawned pid,
|
|
12
|
+
* orphaning grandchildren — so the timeout is handled here instead.
|
|
13
|
+
*
|
|
14
|
+
* @param child - The spawned child process
|
|
15
|
+
* @param timeoutMs - Maximum execution time in milliseconds
|
|
16
|
+
* @param signal - Optional abort signal
|
|
17
|
+
* @param onAbort - Callback when timeout or abort fires (resolves the promise)
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const child = execFile(file, args, options, callback);
|
|
22
|
+
* withTimeoutAndAbort(child, 30_000, signal, () => {
|
|
23
|
+
* resolve({ result: { stdout: "", stderr: "Aborted", exitCode: 1 }, exitCode: 1, durationMs: Date.now() - start, timedOut: false });
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function withTimeoutAndAbort(child, timeoutMs, signal, onAbort) {
|
|
28
|
+
let timedOut = false;
|
|
29
|
+
// RV-11: kill the whole tree, not just the direct child
|
|
30
|
+
const timer = setTimeout(() => {
|
|
31
|
+
timedOut = true;
|
|
32
|
+
killProcessTree(child);
|
|
33
|
+
onAbort("Aborted");
|
|
34
|
+
}, timeoutMs);
|
|
35
|
+
const clearTimer = () => clearTimeout(timer);
|
|
36
|
+
child.once("exit", clearTimer);
|
|
37
|
+
child.once("error", clearTimer);
|
|
38
|
+
if (signal) {
|
|
39
|
+
if (signal.aborted) {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
killProcessTree(child);
|
|
42
|
+
onAbort(signal.reason ?? "Aborted");
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
signal.addEventListener("abort", () => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
killProcessTree(child);
|
|
48
|
+
onAbort(signal.reason ?? "Aborted");
|
|
49
|
+
}, { once: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
get timedOut() { return timedOut; },
|
|
54
|
+
clearTimer,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=timeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../src/timeout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAmB,EACnB,SAAiB,EACjB,MAA+B,EAC/B,OAAiC;IAEjC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,wDAAwD;IACxD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,QAAQ,GAAG,IAAI,CAAC;QAChB,eAAe,CAAC,KAAK,CAAC,CAAC;QACvB,OAAO,CAAC,SAAS,CAAC,CAAC;IACrB,CAAC,EAAE,SAAS,CAAC,CAAC;IAEd,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAC7C,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC/B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAEhC,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,eAAe,CAAC,KAAK,CAAC,CAAC;YACvB,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;gBACpC,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,eAAe,CAAC,KAAK,CAAC,CAAC;gBACvB,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;YACtC,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,QAAQ,KAAK,OAAO,QAAQ,CAAC,CAAC,CAAC;QACnC,UAAU;KACsC,CAAC;AACrD,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Sandbox execution scope — string type, NOT closed union */
|
|
2
|
+
export type SandboxScope = string;
|
|
3
|
+
/**
|
|
4
|
+
* Default sandbox scopes — exported for convenience.
|
|
5
|
+
* Backends register custom scopes via the `createSandbox` backends map.
|
|
6
|
+
*/
|
|
7
|
+
export declare const KNOWN_SANDBOX_SCOPES: readonly ["host", "process", "container"];
|
|
8
|
+
/** Sandbox configuration */
|
|
9
|
+
export interface SandboxConfig {
|
|
10
|
+
defaultTimeoutMs: number;
|
|
11
|
+
/** Execution scope (default: "host" — no isolation) */
|
|
12
|
+
scope?: SandboxScope;
|
|
13
|
+
/** Allowed filesystem paths (for "process" scope) */
|
|
14
|
+
allowedPaths?: string[];
|
|
15
|
+
/** Blocked filesystem paths */
|
|
16
|
+
blockedPaths?: string[];
|
|
17
|
+
/** Allow network access (default: true) */
|
|
18
|
+
allowNetwork?: boolean;
|
|
19
|
+
/** Allowed environment variables */
|
|
20
|
+
allowedEnvVars?: string[];
|
|
21
|
+
/** Allowed commands (for "process" scope) */
|
|
22
|
+
allowedCommands?: Set<string>;
|
|
23
|
+
/** Enable Node.js Permission Model (Node 22+) */
|
|
24
|
+
enablePermissionModel?: boolean;
|
|
25
|
+
readonly metadata?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/** Result of a sandboxed execution */
|
|
28
|
+
export interface SandboxResult<T = unknown> {
|
|
29
|
+
result: T;
|
|
30
|
+
exitCode: number;
|
|
31
|
+
durationMs: number;
|
|
32
|
+
timedOut: boolean;
|
|
33
|
+
/** Resources consumed during execution */
|
|
34
|
+
resources?: {
|
|
35
|
+
cpuTimeMs?: number;
|
|
36
|
+
memoryBytes?: number;
|
|
37
|
+
};
|
|
38
|
+
readonly metadata?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
/** Options for a sandboxed command execution */
|
|
41
|
+
export interface ProcessSandboxExecuteOptions {
|
|
42
|
+
command: string;
|
|
43
|
+
cwd: string;
|
|
44
|
+
timeoutMs: number;
|
|
45
|
+
env?: Record<string, string>;
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Interface for sandbox execution adapters.
|
|
50
|
+
*
|
|
51
|
+
* Implementations can provide different levels of isolation:
|
|
52
|
+
* - `host`: No isolation
|
|
53
|
+
* - `process`: Node.js Permission Model + empty env + command allowlist
|
|
54
|
+
* - `container`: Docker/Firecracker microVM
|
|
55
|
+
*/
|
|
56
|
+
export interface ProcessSandbox {
|
|
57
|
+
/** The scope this sandbox provides */
|
|
58
|
+
readonly scope: SandboxScope;
|
|
59
|
+
/** Execute a command in the sandbox */
|
|
60
|
+
execute(options: ProcessSandboxExecuteOptions): Promise<SandboxResult<{
|
|
61
|
+
stdout: string;
|
|
62
|
+
stderr: string;
|
|
63
|
+
exitCode: number;
|
|
64
|
+
}>>;
|
|
65
|
+
/** Clean up resources */
|
|
66
|
+
destroy(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,oBAAoB,2CAA4C,CAAC;AAE9E,4BAA4B;AAC5B,MAAM,WAAW,aAAa;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,6CAA6C;IAC7C,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,iDAAiD;IACjD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C;AAED,sCAAsC;AACtC,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACxC,MAAM,EAAE,CAAC,CAAC;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,SAAS,CAAC,EAAE;QACV,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C;AAED,gDAAgD;AAChD,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAE7B,uCAAuC;IACvC,OAAO,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,aAAa,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAE7H,yBAAyB;IACzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAU,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vinhnt-sdk/sandbox",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Sandbox primitives for VNT Agent — ProcessSandbox interface, config/result types, fail-closed SandboxUnavailableError, command parsing and tree-scoped process termination",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/vinhnt-develop/vinhnt-sdk.git",
|
|
29
|
+
"directory": "packages/sandbox"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/vinhnt-develop/vinhnt-sdk/tree/main/packages/sandbox",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/vinhnt-develop/vinhnt-sdk/issues"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"vnt",
|
|
37
|
+
"sandbox",
|
|
38
|
+
"process-isolation",
|
|
39
|
+
"security",
|
|
40
|
+
"shell"
|
|
41
|
+
],
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@vinhnt-sdk/schema": "0.1.3",
|
|
47
|
+
"@vinhnt-sdk/security": "0.1.3"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc -b",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"clean": "node -e \"try{require('fs').rmSync('dist',{recursive:true,force:true})}catch(e){}\" && node -e \"try{require('fs').rmSync('tsconfig.tsbuildinfo',{force:true})}catch(e){}\"",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"lint": "eslint ."
|
|
55
|
+
}
|
|
56
|
+
}
|