@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.
Files changed (87) hide show
  1. package/README.md +211 -0
  2. package/dist/executor/base.d.ts +15 -0
  3. package/dist/executor/base.d.ts.map +1 -0
  4. package/dist/executor/base.js +20 -0
  5. package/dist/executor/base.js.map +1 -0
  6. package/dist/executor/darwin.d.ts +16 -0
  7. package/dist/executor/darwin.d.ts.map +1 -0
  8. package/dist/executor/darwin.js +44 -0
  9. package/dist/executor/darwin.js.map +1 -0
  10. package/dist/executor/linux.d.ts +22 -0
  11. package/dist/executor/linux.d.ts.map +1 -0
  12. package/dist/executor/linux.js +50 -0
  13. package/dist/executor/linux.js.map +1 -0
  14. package/dist/executor/select.d.ts +12 -0
  15. package/dist/executor/select.d.ts.map +1 -0
  16. package/dist/executor/select.js +23 -0
  17. package/dist/executor/select.js.map +1 -0
  18. package/dist/executor/types.d.ts +29 -0
  19. package/dist/executor/types.d.ts.map +1 -0
  20. package/dist/executor/types.js +4 -0
  21. package/dist/executor/types.js.map +1 -0
  22. package/dist/index.d.ts +12 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +12 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/linux/bwrap.d.ts +11 -0
  27. package/dist/linux/bwrap.d.ts.map +1 -0
  28. package/dist/linux/bwrap.js +46 -0
  29. package/dist/linux/bwrap.js.map +1 -0
  30. package/dist/linux/cgroup.d.ts +27 -0
  31. package/dist/linux/cgroup.d.ts.map +1 -0
  32. package/dist/linux/cgroup.js +80 -0
  33. package/dist/linux/cgroup.js.map +1 -0
  34. package/dist/linux/unshare.d.ts +11 -0
  35. package/dist/linux/unshare.d.ts.map +1 -0
  36. package/dist/linux/unshare.js +22 -0
  37. package/dist/linux/unshare.js.map +1 -0
  38. package/dist/local-sandbox.d.ts +42 -0
  39. package/dist/local-sandbox.d.ts.map +1 -0
  40. package/dist/local-sandbox.js +235 -0
  41. package/dist/local-sandbox.js.map +1 -0
  42. package/dist/network/ca.d.ts +38 -0
  43. package/dist/network/ca.d.ts.map +1 -0
  44. package/dist/network/ca.js +143 -0
  45. package/dist/network/ca.js.map +1 -0
  46. package/dist/network/proxy.d.ts +46 -0
  47. package/dist/network/proxy.d.ts.map +1 -0
  48. package/dist/network/proxy.js +144 -0
  49. package/dist/network/proxy.js.map +1 -0
  50. package/dist/network/rules.d.ts +23 -0
  51. package/dist/network/rules.d.ts.map +1 -0
  52. package/dist/network/rules.js +64 -0
  53. package/dist/network/rules.js.map +1 -0
  54. package/dist/paths.d.ts +29 -0
  55. package/dist/paths.d.ts.map +1 -0
  56. package/dist/paths.js +129 -0
  57. package/dist/paths.js.map +1 -0
  58. package/dist/platform/detect.d.ts +17 -0
  59. package/dist/platform/detect.d.ts.map +1 -0
  60. package/dist/platform/detect.js +114 -0
  61. package/dist/platform/detect.js.map +1 -0
  62. package/dist/platform/types.d.ts +16 -0
  63. package/dist/platform/types.d.ts.map +1 -0
  64. package/dist/platform/types.js +4 -0
  65. package/dist/platform/types.js.map +1 -0
  66. package/dist/provider.d.ts +33 -0
  67. package/dist/provider.d.ts.map +1 -0
  68. package/dist/provider.js +137 -0
  69. package/dist/provider.js.map +1 -0
  70. package/dist/resources.d.ts +30 -0
  71. package/dist/resources.d.ts.map +1 -0
  72. package/dist/resources.js +94 -0
  73. package/dist/resources.js.map +1 -0
  74. package/dist/seatbelt/profile.d.ts +30 -0
  75. package/dist/seatbelt/profile.d.ts.map +1 -0
  76. package/dist/seatbelt/profile.js +106 -0
  77. package/dist/seatbelt/profile.js.map +1 -0
  78. package/dist/testing.d.ts +22 -0
  79. package/dist/testing.d.ts.map +1 -0
  80. package/dist/testing.js +39 -0
  81. package/dist/testing.js.map +1 -0
  82. package/dist/workspace.d.ts +30 -0
  83. package/dist/workspace.d.ts.map +1 -0
  84. package/dist/workspace.js +68 -0
  85. package/dist/workspace.js.map +1 -0
  86. package/package.json +64 -0
  87. package/src/index.ts +17 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * cgroups v2 Resource Limit Manager
3
+ *
4
+ * Creates a cgroup under /sys/fs/cgroup/ for enforcing memory, CPU, and
5
+ * process limits. Degrades gracefully if the cgroup directory is not writable.
6
+ */
7
+ import { mkdir, writeFile, rm, access } from "node:fs/promises";
8
+ import { constants } from "node:fs";
9
+ import { join } from "node:path";
10
+ const CGROUP_BASE = "/sys/fs/cgroup";
11
+ export class CgroupManager {
12
+ id;
13
+ cgroupPath;
14
+ created = false;
15
+ constructor(id) {
16
+ this.id = id;
17
+ this.cgroupPath = join(CGROUP_BASE, `agentick-${id}`);
18
+ }
19
+ /**
20
+ * Create the cgroup directory and apply resource limits.
21
+ * No-op if cgroups v2 is not available or not writable.
22
+ */
23
+ async create(limits) {
24
+ try {
25
+ await access(CGROUP_BASE, constants.W_OK);
26
+ }
27
+ catch {
28
+ // cgroups not writable — degrade gracefully
29
+ return;
30
+ }
31
+ try {
32
+ await mkdir(this.cgroupPath, { recursive: true });
33
+ this.created = true;
34
+ if (limits.memory) {
35
+ await writeFile(join(this.cgroupPath, "memory.max"), String(limits.memory));
36
+ }
37
+ if (limits.cpu) {
38
+ // cpu.max format: "quota period" (microseconds)
39
+ const period = 100_000; // 100ms
40
+ const quota = Math.round(limits.cpu * period);
41
+ await writeFile(join(this.cgroupPath, "cpu.max"), `${quota} ${period}`);
42
+ }
43
+ if (limits.maxProcesses) {
44
+ await writeFile(join(this.cgroupPath, "pids.max"), String(limits.maxProcesses));
45
+ }
46
+ }
47
+ catch (err) {
48
+ console.warn(`[sandbox-local] Failed to create cgroup ${this.cgroupPath}:`, err);
49
+ this.created = false;
50
+ }
51
+ }
52
+ /**
53
+ * Add a process to this cgroup.
54
+ */
55
+ async addProcess(pid) {
56
+ if (!this.created)
57
+ return;
58
+ try {
59
+ await writeFile(join(this.cgroupPath, "cgroup.procs"), String(pid));
60
+ }
61
+ catch {
62
+ // Process may have already exited
63
+ }
64
+ }
65
+ /**
66
+ * Destroy the cgroup directory.
67
+ */
68
+ async destroy() {
69
+ if (!this.created)
70
+ return;
71
+ try {
72
+ await rm(this.cgroupPath, { recursive: true, force: true });
73
+ }
74
+ catch {
75
+ // Best-effort cleanup
76
+ }
77
+ this.created = false;
78
+ }
79
+ }
80
+ //# sourceMappingURL=cgroup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cgroup.js","sourceRoot":"","sources":["../../src/linux/cgroup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAErC,MAAM,OAAO,aAAa;IAIK;IAHZ,UAAU,CAAS;IAC5B,OAAO,GAAG,KAAK,CAAC;IAExB,YAA6B,EAAU;QAAV,OAAE,GAAF,EAAE,CAAQ;QACrC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC;IACxD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,MAAsB;QACjC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,WAAW,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,4CAA4C;YAC5C,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAClD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YAEpB,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAC9E,CAAC;YAED,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;gBACf,gDAAgD;gBAChD,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,QAAQ;gBAChC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC;gBAC9C,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,GAAG,KAAK,IAAI,MAAM,EAAE,CAAC,CAAC;YAC1E,CAAC;YAED,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;gBACxB,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;YAClF,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,2CAA2C,IAAI,CAAC,UAAU,GAAG,EAAE,GAAG,CAAC,CAAC;YACjF,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,GAAW;QAC1B,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAE1B,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;QACpC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAE1B,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,CAAC;QAAC,MAAM,CAAC;YACP,sBAAsB;QACxB,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * unshare argument builder.
3
+ *
4
+ * Lighter isolation than bubblewrap — uses Linux namespaces directly.
5
+ */
6
+ import type { SpawnOptions } from "../executor/types";
7
+ /**
8
+ * Build unshare argument array for a given set of spawn options.
9
+ */
10
+ export declare function buildUnshareArgs(options: SpawnOptions): string[];
11
+ //# sourceMappingURL=unshare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unshare.d.ts","sourceRoot":"","sources":["../../src/linux/unshare.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,EAAE,CAgBhE"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * unshare argument builder.
3
+ *
4
+ * Lighter isolation than bubblewrap — uses Linux namespaces directly.
5
+ */
6
+ /**
7
+ * Build unshare argument array for a given set of spawn options.
8
+ */
9
+ export function buildUnshareArgs(options) {
10
+ const args = [];
11
+ // PID and mount namespaces
12
+ args.push("--mount", "--pid", "--fork");
13
+ // Network namespace (only if network denied)
14
+ const net = options.permissions.network;
15
+ if (net === false) {
16
+ args.push("--net");
17
+ }
18
+ // User namespace for privilege isolation
19
+ args.push("--user", "--map-root-user");
20
+ return args;
21
+ }
22
+ //# sourceMappingURL=unshare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unshare.js","sourceRoot":"","sources":["../../src/linux/unshare.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAqB;IACpD,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,2BAA2B;IAC3B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAExC,6CAA6C;IAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC;IACxC,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;QAClB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;IAED,yCAAyC;IACzC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAEvC,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * LocalSandbox — implements the Sandbox contract using local OS primitives.
3
+ */
4
+ import type { SandboxHandle, ExecOptions, ExecResult } from "@agentick/sandbox";
5
+ import type { Edit, EditResult } from "@agentick/sandbox";
6
+ import type { CommandExecutor, ResolvedMount, ResolvedPermissions } from "./executor/types";
7
+ import type { ResourceEnforcer } from "./resources";
8
+ import type { NetworkProxyServer } from "./network/proxy";
9
+ export interface LocalSandboxInit {
10
+ id: string;
11
+ workspacePath: string;
12
+ executor: CommandExecutor;
13
+ env: Record<string, string>;
14
+ mounts: ResolvedMount[];
15
+ permissions: ResolvedPermissions;
16
+ resources: ResourceEnforcer;
17
+ proxy?: NetworkProxyServer;
18
+ cleanupWorkspace: boolean;
19
+ destroyWorkspace: () => Promise<void>;
20
+ }
21
+ export declare class LocalSandbox implements SandboxHandle {
22
+ readonly id: string;
23
+ readonly workspacePath: string;
24
+ private readonly executor;
25
+ private readonly env;
26
+ private readonly mounts;
27
+ private readonly permissions;
28
+ private readonly resources;
29
+ private readonly proxy?;
30
+ private readonly cleanupWorkspace;
31
+ private readonly _destroyWorkspace;
32
+ private activeProcesses;
33
+ private destroyed;
34
+ constructor(init: LocalSandboxInit);
35
+ exec(command: string, options?: ExecOptions): Promise<ExecResult>;
36
+ readFile(path: string): Promise<string>;
37
+ writeFile(path: string, content: string): Promise<void>;
38
+ editFile(path: string, edits: Edit[]): Promise<EditResult>;
39
+ destroy(): Promise<void>;
40
+ private assertAlive;
41
+ }
42
+ //# sourceMappingURL=local-sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-sandbox.d.ts","sourceRoot":"","sources":["../src/local-sandbox.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAe,MAAM,mBAAmB,CAAC;AAC7F,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAK1D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,WAAW,EAAE,mBAAmB,CAAC;IACjC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,qBAAa,YAAa,YAAW,aAAa;IAChD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAyB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAC3C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsB;IACxD,OAAO,CAAC,eAAe,CAA2B;IAClD,OAAO,CAAC,SAAS,CAAS;gBAEd,IAAI,EAAE,gBAAgB;IAa5B,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAgEjE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMvC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBvD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAyB1D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAoD9B,OAAO,CAAC,WAAW;CAKpB"}
@@ -0,0 +1,235 @@
1
+ /**
2
+ * LocalSandbox — implements the Sandbox contract using local OS primitives.
3
+ */
4
+ import { readFile, writeFile, rename, unlink, mkdir } from "node:fs/promises";
5
+ import { dirname, join } from "node:path";
6
+ import { randomBytes } from "node:crypto";
7
+ import { applyEdits } from "@agentick/sandbox";
8
+ import { resolveSafePath, filterEnv } from "./paths";
9
+ /** Maximum output per stream (stdout/stderr) — 10MB. */
10
+ const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
11
+ export class LocalSandbox {
12
+ id;
13
+ workspacePath;
14
+ executor;
15
+ env;
16
+ mounts;
17
+ permissions;
18
+ resources;
19
+ proxy;
20
+ cleanupWorkspace;
21
+ _destroyWorkspace;
22
+ activeProcesses = new Set();
23
+ destroyed = false;
24
+ constructor(init) {
25
+ this.id = init.id;
26
+ this.workspacePath = init.workspacePath;
27
+ this.executor = init.executor;
28
+ this.env = init.env;
29
+ this.mounts = init.mounts;
30
+ this.permissions = init.permissions;
31
+ this.resources = init.resources;
32
+ this.proxy = init.proxy;
33
+ this.cleanupWorkspace = init.cleanupWorkspace;
34
+ this._destroyWorkspace = init.destroyWorkspace;
35
+ }
36
+ async exec(command, options) {
37
+ this.assertAlive();
38
+ const cwd = options?.cwd
39
+ ? await resolveSafePath(options.cwd, this.workspacePath, "read", this.mounts)
40
+ : this.workspacePath;
41
+ // Merge environment: base + proxy vars + per-command overrides
42
+ const env = filterEnv({
43
+ ...this.env,
44
+ ...(options?.env ?? {}),
45
+ });
46
+ const child = this.executor.spawn(command, {
47
+ cwd,
48
+ env,
49
+ workspacePath: this.workspacePath,
50
+ mounts: this.mounts,
51
+ permissions: this.permissions,
52
+ });
53
+ this.activeProcesses.add(child);
54
+ this.resources.trackProcess(child);
55
+ // Set up timeout
56
+ const timeoutSignal = this.resources.createTimeoutSignal(options?.timeout);
57
+ let timedOut = false;
58
+ const abortHandler = () => {
59
+ timedOut = true;
60
+ try {
61
+ if (child.pid)
62
+ process.kill(-child.pid, "SIGTERM");
63
+ }
64
+ catch {
65
+ child.kill("SIGTERM");
66
+ }
67
+ };
68
+ timeoutSignal?.addEventListener("abort", abortHandler, { once: true });
69
+ // Collect output with cap
70
+ const stdout = new OutputCollector(MAX_OUTPUT_BYTES, "stdout", options?.onOutput);
71
+ const stderr = new OutputCollector(MAX_OUTPUT_BYTES, "stderr", options?.onOutput);
72
+ child.stdout?.on("data", (chunk) => stdout.write(chunk));
73
+ child.stderr?.on("data", (chunk) => stderr.write(chunk));
74
+ const exitCode = await new Promise((resolve) => {
75
+ child.on("close", (code) => {
76
+ this.activeProcesses.delete(child);
77
+ resolve(code ?? (timedOut ? 124 : 1));
78
+ });
79
+ child.on("error", () => {
80
+ this.activeProcesses.delete(child);
81
+ resolve(timedOut ? 124 : 1);
82
+ });
83
+ });
84
+ timeoutSignal?.removeEventListener("abort", abortHandler);
85
+ return {
86
+ stdout: stdout.toString(),
87
+ stderr: stderr.toString() + (timedOut ? "\n[sandbox: command timed out]" : ""),
88
+ exitCode,
89
+ };
90
+ }
91
+ async readFile(path) {
92
+ this.assertAlive();
93
+ const resolved = await resolveSafePath(path, this.workspacePath, "read", this.mounts);
94
+ return readFile(resolved, "utf-8");
95
+ }
96
+ async writeFile(path, content) {
97
+ this.assertAlive();
98
+ const resolved = await resolveSafePath(path, this.workspacePath, "write", this.mounts);
99
+ // Ensure parent directory exists
100
+ await mkdir(dirname(resolved), { recursive: true });
101
+ // Atomic write: temp + rename
102
+ const tmp = join(dirname(resolved), `.write-${randomBytes(6).toString("hex")}.tmp`);
103
+ try {
104
+ await writeFile(tmp, content, "utf-8");
105
+ await rename(tmp, resolved);
106
+ }
107
+ catch (err) {
108
+ try {
109
+ await unlink(tmp);
110
+ }
111
+ catch {
112
+ // Ignore cleanup errors
113
+ }
114
+ throw err;
115
+ }
116
+ }
117
+ async editFile(path, edits) {
118
+ this.assertAlive();
119
+ const resolved = await resolveSafePath(path, this.workspacePath, "write", this.mounts);
120
+ const source = await readFile(resolved, "utf-8");
121
+ const result = applyEdits(source, edits);
122
+ if (result.applied === 0)
123
+ return result;
124
+ // Atomic write
125
+ const tmp = join(dirname(resolved), `.edit-${randomBytes(6).toString("hex")}.tmp`);
126
+ try {
127
+ await writeFile(tmp, result.content, "utf-8");
128
+ await rename(tmp, resolved);
129
+ }
130
+ catch (err) {
131
+ try {
132
+ await unlink(tmp);
133
+ }
134
+ catch {
135
+ // Ignore cleanup errors
136
+ }
137
+ throw err;
138
+ }
139
+ return result;
140
+ }
141
+ async destroy() {
142
+ if (this.destroyed)
143
+ return;
144
+ this.destroyed = true;
145
+ // Kill all active processes
146
+ for (const child of this.activeProcesses) {
147
+ try {
148
+ if (child.pid) {
149
+ process.kill(-child.pid, "SIGTERM");
150
+ }
151
+ }
152
+ catch {
153
+ child.kill("SIGTERM");
154
+ }
155
+ }
156
+ // Force-kill after 5s
157
+ if (this.activeProcesses.size > 0) {
158
+ await new Promise((resolve) => {
159
+ const timer = setTimeout(() => {
160
+ for (const child of this.activeProcesses) {
161
+ try {
162
+ if (child.pid)
163
+ process.kill(-child.pid, "SIGKILL");
164
+ }
165
+ catch {
166
+ // Already gone
167
+ }
168
+ }
169
+ resolve();
170
+ }, 5000);
171
+ timer.unref();
172
+ // Also resolve early if all processes exit
173
+ const check = setInterval(() => {
174
+ if (this.activeProcesses.size === 0) {
175
+ clearTimeout(timer);
176
+ clearInterval(check);
177
+ resolve();
178
+ }
179
+ }, 100);
180
+ check.unref();
181
+ });
182
+ }
183
+ // Stop proxy
184
+ await this.proxy?.stop();
185
+ // Stop resource enforcement
186
+ await this.resources.stop();
187
+ // Remove workspace
188
+ await this._destroyWorkspace();
189
+ }
190
+ assertAlive() {
191
+ if (this.destroyed) {
192
+ throw new Error(`Sandbox ${this.id} has been destroyed`);
193
+ }
194
+ }
195
+ }
196
+ /**
197
+ * Collects output from a stream with a byte cap and optional streaming callback.
198
+ */
199
+ class OutputCollector {
200
+ maxBytes;
201
+ stream;
202
+ onOutput;
203
+ chunks = [];
204
+ bytes = 0;
205
+ truncated = false;
206
+ constructor(maxBytes, stream, onOutput) {
207
+ this.maxBytes = maxBytes;
208
+ this.stream = stream;
209
+ this.onOutput = onOutput;
210
+ }
211
+ write(chunk) {
212
+ // Always stream to callback (even if we've hit the cap for collection)
213
+ this.onOutput?.({ stream: this.stream, data: chunk.toString() });
214
+ if (this.truncated)
215
+ return;
216
+ if (this.bytes + chunk.length > this.maxBytes) {
217
+ // Take what we can
218
+ const remaining = this.maxBytes - this.bytes;
219
+ if (remaining > 0) {
220
+ this.chunks.push(chunk.subarray(0, remaining));
221
+ this.bytes += remaining;
222
+ }
223
+ this.truncated = true;
224
+ }
225
+ else {
226
+ this.chunks.push(chunk);
227
+ this.bytes += chunk.length;
228
+ }
229
+ }
230
+ toString() {
231
+ const content = Buffer.concat(this.chunks).toString();
232
+ return this.truncated ? content + "\n[sandbox: output truncated at 10MB]" : content;
233
+ }
234
+ }
235
+ //# sourceMappingURL=local-sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-sandbox.js","sourceRoot":"","sources":["../src/local-sandbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Ephemeral CA Certificate Generation
3
+ *
4
+ * Uses the openssl CLI to generate a short-lived CA certificate and
5
+ * per-host certificates for HTTPS interception.
6
+ *
7
+ * Currently used only by tests. When full HTTPS MITM proxy support is
8
+ * added (allowing body-level inspection and URL pattern matching for
9
+ * HTTPS traffic), this will be wired into NetworkProxyServer to
10
+ * generate per-host certs on CONNECT and terminate TLS.
11
+ */
12
+ export interface CACertPaths {
13
+ certPath: string;
14
+ keyPath: string;
15
+ }
16
+ export interface HostCertPaths {
17
+ certPath: string;
18
+ keyPath: string;
19
+ }
20
+ export declare class EphemeralCA {
21
+ private readonly dir;
22
+ private ca?;
23
+ private hostCerts;
24
+ private destroyed;
25
+ constructor();
26
+ /** Get the CA certificate path (generates on first call). */
27
+ get caCertPath(): string | undefined;
28
+ /** Initialize the ephemeral CA. */
29
+ init(): Promise<void>;
30
+ /**
31
+ * Get or generate a certificate for a specific hostname.
32
+ * Caches per-host certs in memory.
33
+ */
34
+ certForHost(hostname: string): Promise<HostCertPaths>;
35
+ /** Clean up all generated certificate files. */
36
+ cleanup(): Promise<void>;
37
+ }
38
+ //# sourceMappingURL=ca.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ca.d.ts","sourceRoot":"","sources":["../../src/network/ca.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,EAAE,CAAC,CAAc;IACzB,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,SAAS,CAAS;;IAM1B,6DAA6D;IAC7D,IAAI,UAAU,IAAI,MAAM,GAAG,SAAS,CAEnC;IAED,mCAAmC;IAC7B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B3B;;;OAGG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA0D3D,gDAAgD;IAC1C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAuB/B"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Ephemeral CA Certificate Generation
3
+ *
4
+ * Uses the openssl CLI to generate a short-lived CA certificate and
5
+ * per-host certificates for HTTPS interception.
6
+ *
7
+ * Currently used only by tests. When full HTTPS MITM proxy support is
8
+ * added (allowing body-level inspection and URL pattern matching for
9
+ * HTTPS traffic), this will be wired into NetworkProxyServer to
10
+ * generate per-host certs on CONNECT and terminate TLS.
11
+ */
12
+ import { execFile } from "node:child_process";
13
+ import { writeFile, unlink, mkdir } from "node:fs/promises";
14
+ import { join } from "node:path";
15
+ import { tmpdir } from "node:os";
16
+ import { randomBytes } from "node:crypto";
17
+ import { promisify } from "node:util";
18
+ const execFileAsync = promisify(execFile);
19
+ export class EphemeralCA {
20
+ dir;
21
+ ca;
22
+ hostCerts = new Map();
23
+ destroyed = false;
24
+ constructor() {
25
+ this.dir = join(tmpdir(), `agentick-ca-${randomBytes(6).toString("hex")}`);
26
+ }
27
+ /** Get the CA certificate path (generates on first call). */
28
+ get caCertPath() {
29
+ return this.ca?.certPath;
30
+ }
31
+ /** Initialize the ephemeral CA. */
32
+ async init() {
33
+ await mkdir(this.dir, { recursive: true, mode: 0o700 });
34
+ const keyPath = join(this.dir, "ca-key.pem");
35
+ const certPath = join(this.dir, "ca-cert.pem");
36
+ // Generate CA key + self-signed cert (1 day validity)
37
+ await execFileAsync("openssl", [
38
+ "req",
39
+ "-x509",
40
+ "-newkey",
41
+ "rsa:2048",
42
+ "-nodes",
43
+ "-days",
44
+ "1",
45
+ "-subj",
46
+ "/CN=Agentick Sandbox CA",
47
+ "-keyout",
48
+ keyPath,
49
+ "-out",
50
+ certPath,
51
+ ]);
52
+ this.ca = { certPath, keyPath };
53
+ }
54
+ /**
55
+ * Get or generate a certificate for a specific hostname.
56
+ * Caches per-host certs in memory.
57
+ */
58
+ async certForHost(hostname) {
59
+ if (this.destroyed)
60
+ throw new Error("CA has been destroyed");
61
+ if (!this.ca)
62
+ throw new Error("CA not initialized");
63
+ const cached = this.hostCerts.get(hostname);
64
+ if (cached)
65
+ return cached;
66
+ const hostKeyPath = join(this.dir, `${hostname}-key.pem`);
67
+ const hostCsrPath = join(this.dir, `${hostname}.csr`);
68
+ const hostCertPath = join(this.dir, `${hostname}-cert.pem`);
69
+ const serial = randomBytes(8).toString("hex");
70
+ // Generate host key + CSR
71
+ await execFileAsync("openssl", [
72
+ "req",
73
+ "-newkey",
74
+ "rsa:2048",
75
+ "-nodes",
76
+ "-subj",
77
+ `/CN=${hostname}`,
78
+ "-keyout",
79
+ hostKeyPath,
80
+ "-out",
81
+ hostCsrPath,
82
+ ]);
83
+ // Create SAN extension config for the host cert
84
+ const extPath = join(this.dir, `${hostname}-ext.cnf`);
85
+ await writeFile(extPath, `subjectAltName=DNS:${hostname}\nbasicConstraints=CA:FALSE\n`);
86
+ // Sign with CA
87
+ await execFileAsync("openssl", [
88
+ "x509",
89
+ "-req",
90
+ "-in",
91
+ hostCsrPath,
92
+ "-CA",
93
+ this.ca.certPath,
94
+ "-CAkey",
95
+ this.ca.keyPath,
96
+ "-set_serial",
97
+ `0x${serial}`,
98
+ "-days",
99
+ "1",
100
+ "-extfile",
101
+ extPath,
102
+ "-out",
103
+ hostCertPath,
104
+ ]);
105
+ // Clean up CSR and ext file
106
+ await Promise.all([safeUnlink(hostCsrPath), safeUnlink(extPath)]);
107
+ const paths = { certPath: hostCertPath, keyPath: hostKeyPath };
108
+ this.hostCerts.set(hostname, paths);
109
+ return paths;
110
+ }
111
+ /** Clean up all generated certificate files. */
112
+ async cleanup() {
113
+ if (this.destroyed)
114
+ return;
115
+ this.destroyed = true;
116
+ const files = [];
117
+ if (this.ca) {
118
+ files.push(this.ca.certPath, this.ca.keyPath);
119
+ }
120
+ for (const cert of this.hostCerts.values()) {
121
+ files.push(cert.certPath, cert.keyPath);
122
+ }
123
+ await Promise.allSettled(files.map(safeUnlink));
124
+ this.hostCerts.clear();
125
+ // Try to remove the directory itself
126
+ try {
127
+ const { rm } = await import("node:fs/promises");
128
+ await rm(this.dir, { recursive: true, force: true });
129
+ }
130
+ catch {
131
+ // Best-effort
132
+ }
133
+ }
134
+ }
135
+ async function safeUnlink(path) {
136
+ try {
137
+ await unlink(path);
138
+ }
139
+ catch {
140
+ // Ignore
141
+ }
142
+ }
143
+ //# sourceMappingURL=ca.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ca.js","sourceRoot":"","sources":["../../src/network/ca.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * HTTP/HTTPS Proxy Server
3
+ *
4
+ * Binds an HTTP proxy server that intercepts HTTP traffic and applies
5
+ * network rules. HTTPS connections are handled at the CONNECT level:
6
+ * allowed connections get a passthrough tunnel, denied ones are rejected.
7
+ *
8
+ * No MITM/TLS termination — HTTPS content is opaque. This covers the
9
+ * primary use case (domain-level allow/deny) without the complexity of
10
+ * CA generation and TLS interception.
11
+ */
12
+ import type { NetworkRule, ProxiedRequest } from "@agentick/sandbox";
13
+ export interface ProxyServerConfig {
14
+ /** Port to bind. 0 = auto-assign. */
15
+ port?: number;
16
+ /** Called for each request (before forwarding/blocking). */
17
+ onRequest?: (req: ProxiedRequest) => void;
18
+ /** Called when a request is blocked. */
19
+ onBlock?: (req: ProxiedRequest) => void;
20
+ /** Maximum audit log entries. Default: 10000. */
21
+ maxAuditEntries?: number;
22
+ }
23
+ export declare class NetworkProxyServer {
24
+ readonly rules: NetworkRule[];
25
+ private readonly config;
26
+ private server?;
27
+ private auditLog;
28
+ private _proxyUrl?;
29
+ private _port?;
30
+ constructor(rules: NetworkRule[], config?: ProxyServerConfig);
31
+ /** The proxy URL (e.g. "http://127.0.0.1:12345"). Available after start(). */
32
+ get proxyUrl(): string;
33
+ /** Start the proxy server. */
34
+ start(): Promise<void>;
35
+ /** Stop the proxy server. */
36
+ stop(): Promise<void>;
37
+ /** Get the audit log of all proxied requests. */
38
+ getAuditLog(): ProxiedRequest[];
39
+ /** Handle an HTTP request (non-CONNECT). */
40
+ private handleHttpRequest;
41
+ /** Handle a CONNECT request (HTTPS tunnel). */
42
+ private handleConnect;
43
+ /** Log a request and trim the audit log if needed. */
44
+ private logRequest;
45
+ }
46
+ //# sourceMappingURL=proxy.d.ts.map