@agentick/sandbox-local 0.2.1
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/README.md +211 -0
- package/dist/executor/base.d.ts +15 -0
- package/dist/executor/base.d.ts.map +1 -0
- package/dist/executor/base.js +20 -0
- package/dist/executor/base.js.map +1 -0
- package/dist/executor/darwin.d.ts +16 -0
- package/dist/executor/darwin.d.ts.map +1 -0
- package/dist/executor/darwin.js +44 -0
- package/dist/executor/darwin.js.map +1 -0
- package/dist/executor/linux.d.ts +22 -0
- package/dist/executor/linux.d.ts.map +1 -0
- package/dist/executor/linux.js +50 -0
- package/dist/executor/linux.js.map +1 -0
- package/dist/executor/select.d.ts +12 -0
- package/dist/executor/select.d.ts.map +1 -0
- package/dist/executor/select.js +23 -0
- package/dist/executor/select.js.map +1 -0
- package/dist/executor/types.d.ts +29 -0
- package/dist/executor/types.d.ts.map +1 -0
- package/dist/executor/types.js +4 -0
- package/dist/executor/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/linux/bwrap.d.ts +11 -0
- package/dist/linux/bwrap.d.ts.map +1 -0
- package/dist/linux/bwrap.js +46 -0
- package/dist/linux/bwrap.js.map +1 -0
- package/dist/linux/cgroup.d.ts +27 -0
- package/dist/linux/cgroup.d.ts.map +1 -0
- package/dist/linux/cgroup.js +80 -0
- package/dist/linux/cgroup.js.map +1 -0
- package/dist/linux/unshare.d.ts +11 -0
- package/dist/linux/unshare.d.ts.map +1 -0
- package/dist/linux/unshare.js +22 -0
- package/dist/linux/unshare.js.map +1 -0
- package/dist/local-sandbox.d.ts +42 -0
- package/dist/local-sandbox.d.ts.map +1 -0
- package/dist/local-sandbox.js +235 -0
- package/dist/local-sandbox.js.map +1 -0
- package/dist/network/ca.d.ts +38 -0
- package/dist/network/ca.d.ts.map +1 -0
- package/dist/network/ca.js +143 -0
- package/dist/network/ca.js.map +1 -0
- package/dist/network/proxy.d.ts +46 -0
- package/dist/network/proxy.d.ts.map +1 -0
- package/dist/network/proxy.js +144 -0
- package/dist/network/proxy.js.map +1 -0
- package/dist/network/rules.d.ts +23 -0
- package/dist/network/rules.d.ts.map +1 -0
- package/dist/network/rules.js +64 -0
- package/dist/network/rules.js.map +1 -0
- package/dist/paths.d.ts +29 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +129 -0
- package/dist/paths.js.map +1 -0
- package/dist/platform/detect.d.ts +17 -0
- package/dist/platform/detect.d.ts.map +1 -0
- package/dist/platform/detect.js +114 -0
- package/dist/platform/detect.js.map +1 -0
- package/dist/platform/types.d.ts +16 -0
- package/dist/platform/types.d.ts.map +1 -0
- package/dist/platform/types.js +4 -0
- package/dist/platform/types.js.map +1 -0
- package/dist/provider.d.ts +33 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +137 -0
- package/dist/provider.js.map +1 -0
- package/dist/resources.d.ts +30 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +94 -0
- package/dist/resources.js.map +1 -0
- package/dist/seatbelt/profile.d.ts +30 -0
- package/dist/seatbelt/profile.d.ts.map +1 -0
- package/dist/seatbelt/profile.js +106 -0
- package/dist/seatbelt/profile.js.map +1 -0
- package/dist/testing.d.ts +22 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +39 -0
- package/dist/testing.js.map +1 -0
- package/dist/workspace.d.ts +30 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +68 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +64 -0
- package/src/index.ts +17 -0
package/dist/provider.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Sandbox Provider
|
|
3
|
+
*
|
|
4
|
+
* Factory function that creates a SandboxProvider backed by OS-level sandboxing.
|
|
5
|
+
*/
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { detectCapabilities, selectStrategy } from "./platform/detect";
|
|
8
|
+
import { selectExecutor } from "./executor/select";
|
|
9
|
+
import { createWorkspace, destroyWorkspace, resolveMounts } from "./workspace";
|
|
10
|
+
import { filterEnv } from "./paths";
|
|
11
|
+
import { ResourceEnforcer } from "./resources";
|
|
12
|
+
import { CgroupManager } from "./linux/cgroup";
|
|
13
|
+
import { LocalSandbox } from "./local-sandbox";
|
|
14
|
+
import { NetworkProxyServer } from "./network/proxy";
|
|
15
|
+
/**
|
|
16
|
+
* Create a local sandbox provider.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { localProvider } from "@agentick/sandbox-local";
|
|
21
|
+
*
|
|
22
|
+
* const provider = localProvider();
|
|
23
|
+
* const sandbox = await provider.create({ workspace: true });
|
|
24
|
+
* const result = await sandbox.exec("echo hello");
|
|
25
|
+
* await sandbox.destroy();
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function localProvider(config) {
|
|
29
|
+
const cleanupDefault = config?.cleanupWorkspace ?? true;
|
|
30
|
+
return {
|
|
31
|
+
name: "local",
|
|
32
|
+
async create(options) {
|
|
33
|
+
const caps = await detectCapabilities();
|
|
34
|
+
const strategy = selectStrategy(caps, config?.strategy);
|
|
35
|
+
// Workspace
|
|
36
|
+
const workspace = await createWorkspace(options.workspace, config?.tmpBase);
|
|
37
|
+
const mounts = await resolveMounts(options.mounts);
|
|
38
|
+
// Permissions
|
|
39
|
+
const permissions = resolvePermissions(options.permissions ?? {}, workspace.path, mounts.map((m) => ({ path: m.hostPath, mode: m.mode })));
|
|
40
|
+
// CgroupManager (Linux only)
|
|
41
|
+
let cgroup;
|
|
42
|
+
if (caps.hasCgroupsV2 && options.limits) {
|
|
43
|
+
const id = randomBytes(4).toString("hex");
|
|
44
|
+
cgroup = new CgroupManager(id);
|
|
45
|
+
await cgroup.create(options.limits);
|
|
46
|
+
}
|
|
47
|
+
// Executor
|
|
48
|
+
const executor = selectExecutor(strategy, cgroup);
|
|
49
|
+
// Environment
|
|
50
|
+
const baseEnv = {
|
|
51
|
+
HOME: workspace.path,
|
|
52
|
+
PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin",
|
|
53
|
+
TERM: "dumb",
|
|
54
|
+
};
|
|
55
|
+
if (options.permissions?.inheritEnv) {
|
|
56
|
+
Object.assign(baseEnv, filterEnv(process.env));
|
|
57
|
+
}
|
|
58
|
+
if (options.env) {
|
|
59
|
+
Object.assign(baseEnv, options.env);
|
|
60
|
+
}
|
|
61
|
+
// Network proxy
|
|
62
|
+
let proxy;
|
|
63
|
+
const netRules = Array.isArray(options.permissions?.net)
|
|
64
|
+
? options.permissions.net
|
|
65
|
+
: undefined;
|
|
66
|
+
if (netRules && netRules.length > 0) {
|
|
67
|
+
proxy = new NetworkProxyServer(netRules, config?.network);
|
|
68
|
+
await proxy.start();
|
|
69
|
+
// Inject proxy env vars
|
|
70
|
+
baseEnv.HTTP_PROXY = proxy.proxyUrl;
|
|
71
|
+
baseEnv.http_proxy = proxy.proxyUrl;
|
|
72
|
+
baseEnv.HTTPS_PROXY = proxy.proxyUrl;
|
|
73
|
+
baseEnv.https_proxy = proxy.proxyUrl;
|
|
74
|
+
}
|
|
75
|
+
// Resource enforcement
|
|
76
|
+
const resources = new ResourceEnforcer(workspace.path, options.limits ?? {});
|
|
77
|
+
await resources.start();
|
|
78
|
+
const sandboxId = randomBytes(8).toString("hex");
|
|
79
|
+
return new LocalSandbox({
|
|
80
|
+
id: sandboxId,
|
|
81
|
+
workspacePath: workspace.path,
|
|
82
|
+
executor,
|
|
83
|
+
env: baseEnv,
|
|
84
|
+
mounts,
|
|
85
|
+
permissions,
|
|
86
|
+
resources,
|
|
87
|
+
proxy,
|
|
88
|
+
cleanupWorkspace: cleanupDefault,
|
|
89
|
+
destroyWorkspace: () => destroyWorkspace(workspace.path, workspace.autoCreated && cleanupDefault),
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
async restore(snapshot) {
|
|
93
|
+
const caps = await detectCapabilities();
|
|
94
|
+
const strategy = selectStrategy(caps, config?.strategy);
|
|
95
|
+
const executor = selectExecutor(strategy);
|
|
96
|
+
const resources = new ResourceEnforcer(snapshot.workspacePath, {});
|
|
97
|
+
await resources.start();
|
|
98
|
+
return new LocalSandbox({
|
|
99
|
+
id: snapshot.id,
|
|
100
|
+
workspacePath: snapshot.workspacePath,
|
|
101
|
+
executor,
|
|
102
|
+
env: {
|
|
103
|
+
HOME: snapshot.workspacePath,
|
|
104
|
+
PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin",
|
|
105
|
+
TERM: "dumb",
|
|
106
|
+
},
|
|
107
|
+
mounts: [],
|
|
108
|
+
permissions: {
|
|
109
|
+
readPaths: [snapshot.workspacePath],
|
|
110
|
+
writePaths: [snapshot.workspacePath],
|
|
111
|
+
network: false,
|
|
112
|
+
childProcess: true,
|
|
113
|
+
},
|
|
114
|
+
resources,
|
|
115
|
+
cleanupWorkspace: false,
|
|
116
|
+
destroyWorkspace: async () => { },
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function resolvePermissions(perms, workspacePath, mounts) {
|
|
122
|
+
const readPaths = [workspacePath];
|
|
123
|
+
const writePaths = [workspacePath];
|
|
124
|
+
for (const mount of mounts) {
|
|
125
|
+
readPaths.push(mount.path);
|
|
126
|
+
if (mount.mode === "rw") {
|
|
127
|
+
writePaths.push(mount.path);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
readPaths,
|
|
132
|
+
writePaths,
|
|
133
|
+
network: perms.net ?? false,
|
|
134
|
+
childProcess: perms.childProcess ?? true,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Enforcement
|
|
3
|
+
*
|
|
4
|
+
* Timeout management, disk monitoring, and process tracking.
|
|
5
|
+
* cgroups-based limits are handled separately by CgroupManager (Linux only).
|
|
6
|
+
*/
|
|
7
|
+
import type { ChildProcess } from "node:child_process";
|
|
8
|
+
import type { ResourceLimits } from "@agentick/sandbox";
|
|
9
|
+
export declare class ResourceEnforcer {
|
|
10
|
+
private readonly workspacePath;
|
|
11
|
+
private readonly limits;
|
|
12
|
+
private diskTimer?;
|
|
13
|
+
private trackedProcesses;
|
|
14
|
+
private stopped;
|
|
15
|
+
constructor(workspacePath: string, limits: ResourceLimits);
|
|
16
|
+
/** Start resource monitoring. */
|
|
17
|
+
start(): Promise<void>;
|
|
18
|
+
/** Track a child process for cleanup on resource violations. */
|
|
19
|
+
trackProcess(child: ChildProcess): void;
|
|
20
|
+
/**
|
|
21
|
+
* Create an AbortSignal that fires after the given timeout.
|
|
22
|
+
* Falls back to the global timeout limit if no per-command timeout.
|
|
23
|
+
*/
|
|
24
|
+
createTimeoutSignal(timeout?: number): AbortSignal | undefined;
|
|
25
|
+
/** Stop all monitoring and kill tracked processes. */
|
|
26
|
+
stop(): Promise<void>;
|
|
27
|
+
private checkDisk;
|
|
28
|
+
private killAllProcesses;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=resources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAMxD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,SAAS,CAAC,CAAiC;IACnD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,OAAO,CAAS;gBAEZ,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc;IAKzD,iCAAiC;IAC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,gEAAgE;IAChE,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAKvC;;;OAGG;IACH,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAM9D,sDAAsD;IAChD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAYb,SAAS;IAevB,OAAO,CAAC,gBAAgB;CAuBzB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Enforcement
|
|
3
|
+
*
|
|
4
|
+
* Timeout management, disk monitoring, and process tracking.
|
|
5
|
+
* cgroups-based limits are handled separately by CgroupManager (Linux only).
|
|
6
|
+
*/
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
const DISK_POLL_INTERVAL = 5000; // 5 seconds
|
|
11
|
+
export class ResourceEnforcer {
|
|
12
|
+
workspacePath;
|
|
13
|
+
limits;
|
|
14
|
+
diskTimer;
|
|
15
|
+
trackedProcesses = new Set();
|
|
16
|
+
stopped = false;
|
|
17
|
+
constructor(workspacePath, limits) {
|
|
18
|
+
this.workspacePath = workspacePath;
|
|
19
|
+
this.limits = limits;
|
|
20
|
+
}
|
|
21
|
+
/** Start resource monitoring. */
|
|
22
|
+
async start() {
|
|
23
|
+
if (this.limits.disk) {
|
|
24
|
+
this.diskTimer = setInterval(() => this.checkDisk(), DISK_POLL_INTERVAL);
|
|
25
|
+
this.diskTimer.unref();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Track a child process for cleanup on resource violations. */
|
|
29
|
+
trackProcess(child) {
|
|
30
|
+
this.trackedProcesses.add(child);
|
|
31
|
+
child.on("exit", () => this.trackedProcesses.delete(child));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create an AbortSignal that fires after the given timeout.
|
|
35
|
+
* Falls back to the global timeout limit if no per-command timeout.
|
|
36
|
+
*/
|
|
37
|
+
createTimeoutSignal(timeout) {
|
|
38
|
+
const ms = timeout ?? this.limits.timeout;
|
|
39
|
+
if (!ms)
|
|
40
|
+
return undefined;
|
|
41
|
+
return AbortSignal.timeout(ms);
|
|
42
|
+
}
|
|
43
|
+
/** Stop all monitoring and kill tracked processes. */
|
|
44
|
+
async stop() {
|
|
45
|
+
if (this.stopped)
|
|
46
|
+
return;
|
|
47
|
+
this.stopped = true;
|
|
48
|
+
if (this.diskTimer) {
|
|
49
|
+
clearInterval(this.diskTimer);
|
|
50
|
+
this.diskTimer = undefined;
|
|
51
|
+
}
|
|
52
|
+
this.killAllProcesses();
|
|
53
|
+
}
|
|
54
|
+
async checkDisk() {
|
|
55
|
+
if (!this.limits.disk || this.stopped)
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
// du -sk works on both macOS and Linux (-sb is Linux-only)
|
|
59
|
+
const { stdout } = await execFileAsync("du", ["-sk", this.workspacePath]);
|
|
60
|
+
const bytes = parseInt(stdout.split("\t")[0], 10) * 1024;
|
|
61
|
+
if (bytes > this.limits.disk) {
|
|
62
|
+
this.killAllProcesses();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// du may fail if workspace was destroyed — ignore
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
killAllProcesses() {
|
|
70
|
+
for (const child of this.trackedProcesses) {
|
|
71
|
+
try {
|
|
72
|
+
// Kill process group if possible
|
|
73
|
+
if (child.pid) {
|
|
74
|
+
process.kill(-child.pid, "SIGTERM");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Process may have already exited
|
|
79
|
+
}
|
|
80
|
+
// Forceful kill after 5 seconds
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
try {
|
|
83
|
+
if (child.pid) {
|
|
84
|
+
process.kill(-child.pid, "SIGKILL");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Already gone
|
|
89
|
+
}
|
|
90
|
+
}, 5000).unref();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS Seatbelt Profile Generation
|
|
3
|
+
*
|
|
4
|
+
* Compiles SpawnOptions into Apple Seatbelt Profile Language (SBPL).
|
|
5
|
+
*
|
|
6
|
+
* Security model: "safe by default"
|
|
7
|
+
*
|
|
8
|
+
* SBPL resolves conflicting rules by specificity — more specific wins.
|
|
9
|
+
* At equal specificity, deny beats allow. This lets us:
|
|
10
|
+
*
|
|
11
|
+
* 1. (allow file-read*) — system reads (needed for bash)
|
|
12
|
+
* 2. (deny file-read* (subpath "/Users")) — deny user home dirs
|
|
13
|
+
* 3. (allow file-read* (subpath "{workspace}")) — re-allow workspace
|
|
14
|
+
*
|
|
15
|
+
* The deny at step 2 is more specific than the allow at step 1, so it wins
|
|
16
|
+
* for paths under /Users. The allow at step 3 is more specific than the deny
|
|
17
|
+
* at step 2, so workspace access works even if it's under /Users.
|
|
18
|
+
*
|
|
19
|
+
* This prevents sandboxed processes from reading SSH keys, credentials,
|
|
20
|
+
* browser profiles, and other sensitive user data — while still allowing
|
|
21
|
+
* system libraries and executables to load normally.
|
|
22
|
+
*
|
|
23
|
+
* Write restrictions are always tight: only workspace, mounts, /tmp, /dev.
|
|
24
|
+
*/
|
|
25
|
+
import type { SpawnOptions } from "../executor/types";
|
|
26
|
+
/**
|
|
27
|
+
* Compile a seatbelt profile string from spawn options.
|
|
28
|
+
*/
|
|
29
|
+
export declare function compileSeatbeltProfile(options: SpawnOptions): string;
|
|
30
|
+
//# sourceMappingURL=profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/seatbelt/profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAqBtD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAqEpE"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS Seatbelt Profile Generation
|
|
3
|
+
*
|
|
4
|
+
* Compiles SpawnOptions into Apple Seatbelt Profile Language (SBPL).
|
|
5
|
+
*
|
|
6
|
+
* Security model: "safe by default"
|
|
7
|
+
*
|
|
8
|
+
* SBPL resolves conflicting rules by specificity — more specific wins.
|
|
9
|
+
* At equal specificity, deny beats allow. This lets us:
|
|
10
|
+
*
|
|
11
|
+
* 1. (allow file-read*) — system reads (needed for bash)
|
|
12
|
+
* 2. (deny file-read* (subpath "/Users")) — deny user home dirs
|
|
13
|
+
* 3. (allow file-read* (subpath "{workspace}")) — re-allow workspace
|
|
14
|
+
*
|
|
15
|
+
* The deny at step 2 is more specific than the allow at step 1, so it wins
|
|
16
|
+
* for paths under /Users. The allow at step 3 is more specific than the deny
|
|
17
|
+
* at step 2, so workspace access works even if it's under /Users.
|
|
18
|
+
*
|
|
19
|
+
* This prevents sandboxed processes from reading SSH keys, credentials,
|
|
20
|
+
* browser profiles, and other sensitive user data — while still allowing
|
|
21
|
+
* system libraries and executables to load normally.
|
|
22
|
+
*
|
|
23
|
+
* Write restrictions are always tight: only workspace, mounts, /tmp, /dev.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Paths that sandboxed processes cannot read.
|
|
27
|
+
*
|
|
28
|
+
* /Users — home directories (SSH keys, .env, browser profiles, credentials)
|
|
29
|
+
* /private/var/root — root's home directory
|
|
30
|
+
* /Volumes — mounted drives, encrypted volumes, network shares
|
|
31
|
+
* /Network — network-mounted resources
|
|
32
|
+
* /Library/Keychains — system-level keychains and certificates
|
|
33
|
+
* /private/var/db/dslocal — local directory service (user account data, password hashes)
|
|
34
|
+
*/
|
|
35
|
+
const DENIED_READ_PATHS = [
|
|
36
|
+
"/Users",
|
|
37
|
+
"/private/var/root",
|
|
38
|
+
"/Volumes",
|
|
39
|
+
"/Network",
|
|
40
|
+
"/Library/Keychains",
|
|
41
|
+
"/private/var/db/dslocal",
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Compile a seatbelt profile string from spawn options.
|
|
45
|
+
*/
|
|
46
|
+
export function compileSeatbeltProfile(options) {
|
|
47
|
+
const lines = [];
|
|
48
|
+
const emit = (line) => lines.push(line);
|
|
49
|
+
const comment = (text) => emit(`\n;; ${text}`);
|
|
50
|
+
const allow = (...parts) => emit(`(allow ${parts.join(" ")})`);
|
|
51
|
+
const deny = (...parts) => emit(`(deny ${parts.join(" ")})`);
|
|
52
|
+
const subpath = (p) => `(subpath "${p}")`;
|
|
53
|
+
emit("(version 1)");
|
|
54
|
+
deny("default");
|
|
55
|
+
// Process execution
|
|
56
|
+
comment("Process execution");
|
|
57
|
+
allow("process*");
|
|
58
|
+
allow("signal");
|
|
59
|
+
allow("sysctl*");
|
|
60
|
+
// File reads — allow system, deny sensitive, re-allow workspace/mounts.
|
|
61
|
+
// SBPL specificity: subpath filter > unfiltered, so deny(subpath) > allow(*).
|
|
62
|
+
comment("File reads — safe by default");
|
|
63
|
+
allow("file-read*");
|
|
64
|
+
comment("Deny reads to sensitive paths (home dirs, volumes, keychains, etc.)");
|
|
65
|
+
for (const p of DENIED_READ_PATHS) {
|
|
66
|
+
deny("file-read*", subpath(p));
|
|
67
|
+
}
|
|
68
|
+
// Re-allow workspace reads (more specific than /Users deny)
|
|
69
|
+
comment("Re-allow workspace reads");
|
|
70
|
+
allow("file-read*", subpath(options.workspacePath));
|
|
71
|
+
// Re-allow mount reads
|
|
72
|
+
if (options.mounts.length > 0) {
|
|
73
|
+
comment("Re-allow mount reads");
|
|
74
|
+
for (const mount of options.mounts) {
|
|
75
|
+
allow("file-read*", subpath(mount.hostPath));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// File writes — restricted to workspace, mounts, and temp
|
|
79
|
+
comment("File writes (restricted)");
|
|
80
|
+
allow("file-write*", subpath(options.workspacePath));
|
|
81
|
+
allow("file-write*", subpath("/private/tmp"));
|
|
82
|
+
allow("file-write*", subpath("/tmp"));
|
|
83
|
+
allow("file-write*", subpath("/dev"));
|
|
84
|
+
// Mount writes
|
|
85
|
+
if (options.mounts.length > 0) {
|
|
86
|
+
comment("Mount writes");
|
|
87
|
+
for (const mount of options.mounts) {
|
|
88
|
+
if (mount.mode === "rw") {
|
|
89
|
+
allow("file-write*", subpath(mount.hostPath));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Network
|
|
94
|
+
comment("Network");
|
|
95
|
+
const net = options.permissions.network;
|
|
96
|
+
if (net === false) {
|
|
97
|
+
deny("network*");
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// net === true or NetworkRule[] — allow all at seatbelt level.
|
|
101
|
+
// NetworkRule enforcement happens via the proxy layer, not seatbelt.
|
|
102
|
+
allow("network*");
|
|
103
|
+
}
|
|
104
|
+
return lines.join("\n") + "\n";
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/seatbelt/profile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Local Testing Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helpers for testing sandbox consumers and the local provider itself.
|
|
5
|
+
*/
|
|
6
|
+
import type { LocalProviderConfig } from "./provider";
|
|
7
|
+
import type { SandboxProvider } from "@agentick/sandbox";
|
|
8
|
+
import type { PlatformCapabilities } from "./platform/types";
|
|
9
|
+
/**
|
|
10
|
+
* Create a test provider with unsandboxed executor by default.
|
|
11
|
+
* Useful for CI and cross-platform tests that don't need OS isolation.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createTestProvider(config?: Partial<LocalProviderConfig>): SandboxProvider;
|
|
14
|
+
/** Whether the current platform is macOS. */
|
|
15
|
+
export declare const isDarwin: boolean;
|
|
16
|
+
/** Whether the current platform is Linux. */
|
|
17
|
+
export declare const isLinux: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Create mock PlatformCapabilities for testing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createMockCapabilities(overrides?: Partial<PlatformCapabilities>): PlatformCapabilities;
|
|
22
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,eAAe,CAMzF;AAED,6CAA6C;AAC7C,eAAO,MAAM,QAAQ,SAAgC,CAAC;AAEtD,6CAA6C;AAC7C,eAAO,MAAM,OAAO,SAA+B,CAAC;AAEpD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GACxC,oBAAoB,CAatB"}
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Local Testing Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helpers for testing sandbox consumers and the local provider itself.
|
|
5
|
+
*/
|
|
6
|
+
import { localProvider } from "./provider";
|
|
7
|
+
/**
|
|
8
|
+
* Create a test provider with unsandboxed executor by default.
|
|
9
|
+
* Useful for CI and cross-platform tests that don't need OS isolation.
|
|
10
|
+
*/
|
|
11
|
+
export function createTestProvider(config) {
|
|
12
|
+
return localProvider({
|
|
13
|
+
strategy: "none",
|
|
14
|
+
cleanupWorkspace: true,
|
|
15
|
+
...config,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Whether the current platform is macOS. */
|
|
19
|
+
export const isDarwin = process.platform === "darwin";
|
|
20
|
+
/** Whether the current platform is Linux. */
|
|
21
|
+
export const isLinux = process.platform === "linux";
|
|
22
|
+
/**
|
|
23
|
+
* Create mock PlatformCapabilities for testing.
|
|
24
|
+
*/
|
|
25
|
+
export function createMockCapabilities(overrides) {
|
|
26
|
+
return {
|
|
27
|
+
platform: "darwin",
|
|
28
|
+
arch: "arm64",
|
|
29
|
+
hasSandboxExec: true,
|
|
30
|
+
hasBwrap: false,
|
|
31
|
+
hasUnshare: false,
|
|
32
|
+
hasCgroupsV2: false,
|
|
33
|
+
userNamespaces: false,
|
|
34
|
+
uid: 501,
|
|
35
|
+
recommended: "seatbelt",
|
|
36
|
+
...overrides,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAK3C;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAqC;IACtE,OAAO,aAAa,CAAC;QACnB,QAAQ,EAAE,MAAM;QAChB,gBAAgB,EAAE,IAAI;QACtB,GAAG,MAAM;KACV,CAAC,CAAC;AACL,CAAC;AAED,6CAA6C;AAC7C,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC;AAEtD,6CAA6C;AAC7C,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AAEpD;;GAEG;AACH,MAAM,UAAU,sBAAsB,CACpC,SAAyC;IAEzC,OAAO;QACL,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,OAAO;QACb,cAAc,EAAE,IAAI;QACpB,QAAQ,EAAE,KAAK;QACf,UAAU,EAAE,KAAK;QACjB,YAAY,EAAE,KAAK;QACnB,cAAc,EAAE,KAAK;QACrB,GAAG,EAAE,GAAG;QACR,WAAW,EAAE,UAAU;QACvB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Management
|
|
3
|
+
*
|
|
4
|
+
* Create, validate, and destroy workspace directories. Resolve mounts
|
|
5
|
+
* from SandboxCreateOptions into host-path ResolvedMounts.
|
|
6
|
+
*/
|
|
7
|
+
import type { Mount } from "@agentick/sandbox";
|
|
8
|
+
import type { ResolvedMount } from "./executor/types";
|
|
9
|
+
/**
|
|
10
|
+
* Create a workspace directory.
|
|
11
|
+
*
|
|
12
|
+
* @param workspace - Explicit path, or `true` for auto-generated temp dir
|
|
13
|
+
* @param tmpBase - Base directory for temp workspaces (default: os.tmpdir())
|
|
14
|
+
* @returns Object with the workspace path and whether it was auto-created
|
|
15
|
+
*/
|
|
16
|
+
export declare function createWorkspace(workspace: string | true | undefined, tmpBase?: string): Promise<{
|
|
17
|
+
path: string;
|
|
18
|
+
autoCreated: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Destroy a workspace directory.
|
|
22
|
+
* Only removes auto-created workspaces (safety: never delete user-specified dirs).
|
|
23
|
+
*/
|
|
24
|
+
export declare function destroyWorkspace(path: string, autoCreated: boolean): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve mounts from user-specified Mount[] to ResolvedMount[].
|
|
27
|
+
* Validates that host paths exist and are accessible.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveMounts(mounts?: Mount[]): Promise<ResolvedMount[]>;
|
|
30
|
+
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,OAAO,GAAE,MAAiB,GACzB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAYjD;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAQxF;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,GAAE,KAAK,EAAO,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAqBlF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Management
|
|
3
|
+
*
|
|
4
|
+
* Create, validate, and destroy workspace directories. Resolve mounts
|
|
5
|
+
* from SandboxCreateOptions into host-path ResolvedMounts.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdir, rm, access, realpath } from "node:fs/promises";
|
|
8
|
+
import { constants } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { randomBytes } from "node:crypto";
|
|
12
|
+
/**
|
|
13
|
+
* Create a workspace directory.
|
|
14
|
+
*
|
|
15
|
+
* @param workspace - Explicit path, or `true` for auto-generated temp dir
|
|
16
|
+
* @param tmpBase - Base directory for temp workspaces (default: os.tmpdir())
|
|
17
|
+
* @returns Object with the workspace path and whether it was auto-created
|
|
18
|
+
*/
|
|
19
|
+
export async function createWorkspace(workspace, tmpBase = tmpdir()) {
|
|
20
|
+
if (workspace === true || workspace === undefined) {
|
|
21
|
+
const id = randomBytes(8).toString("hex");
|
|
22
|
+
const raw = join(tmpBase, `agentick-sandbox-${id}`);
|
|
23
|
+
await mkdir(raw, { recursive: true, mode: 0o700 });
|
|
24
|
+
// Always return realpath'd (e.g. macOS /var → /private/var)
|
|
25
|
+
return { path: await realpath(raw), autoCreated: true };
|
|
26
|
+
}
|
|
27
|
+
// Explicit path — ensure it exists
|
|
28
|
+
await mkdir(workspace, { recursive: true });
|
|
29
|
+
return { path: await realpath(workspace), autoCreated: false };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Destroy a workspace directory.
|
|
33
|
+
* Only removes auto-created workspaces (safety: never delete user-specified dirs).
|
|
34
|
+
*/
|
|
35
|
+
export async function destroyWorkspace(path, autoCreated) {
|
|
36
|
+
if (!autoCreated)
|
|
37
|
+
return;
|
|
38
|
+
try {
|
|
39
|
+
await rm(path, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.warn(`[sandbox-local] Failed to destroy workspace ${path}:`, err);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve mounts from user-specified Mount[] to ResolvedMount[].
|
|
47
|
+
* Validates that host paths exist and are accessible.
|
|
48
|
+
*/
|
|
49
|
+
export async function resolveMounts(mounts = []) {
|
|
50
|
+
const resolved = [];
|
|
51
|
+
for (const mount of mounts) {
|
|
52
|
+
// Validate host path exists
|
|
53
|
+
try {
|
|
54
|
+
await access(mount.host, constants.R_OK);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new Error(`Mount host path not accessible: ${mount.host}`);
|
|
58
|
+
}
|
|
59
|
+
const hostPath = await realpath(mount.host);
|
|
60
|
+
resolved.push({
|
|
61
|
+
hostPath,
|
|
62
|
+
sandboxPath: mount.sandbox,
|
|
63
|
+
mode: mount.mode ?? "rw",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return resolved;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,SAAoC,EACpC,UAAkB,MAAM,EAAE;IAE1B,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAClD,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,CAAC,CAAC;QACpD,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD,4DAA4D;QAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAC1D,CAAC;IAED,mCAAmC;IACnC,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAY,EAAE,WAAoB;IACvE,IAAI,CAAC,WAAW;QAAE,OAAO;IAEzB,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,+CAA+C,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,SAAkB,EAAE;IACtD,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,4BAA4B;QAC5B,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,mCAAmC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACnE,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAE5C,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ;YACR,WAAW,EAAE,KAAK,CAAC,OAAO;YAC1B,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,IAAI;SACzB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|