@bunny-agent/sandbox-srt 0.9.52-beta.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/LICENSE +201 -0
- package/dist/__tests__/srt-sandbox.test.d.ts +2 -0
- package/dist/__tests__/srt-sandbox.test.d.ts.map +1 -0
- package/dist/__tests__/srt-sandbox.test.js +264 -0
- package/dist/__tests__/srt-sandbox.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/srt-sandbox.d.ts +126 -0
- package/dist/srt-sandbox.d.ts.map +1 -0
- package/dist/srt-sandbox.js +216 -0
- package/dist/srt-sandbox.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"srt-sandbox.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/srt-sandbox.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { buildInstallHint, SrtSandbox } from "../srt-sandbox.js";
|
|
7
|
+
/**
|
|
8
|
+
* These are real isolation tests: they execute commands through the actual
|
|
9
|
+
* srt wrapper (bubblewrap on Linux, Seatbelt on macOS). They are skipped on
|
|
10
|
+
* hosts where the platform sandboxing primitive is unavailable (e.g. Linux
|
|
11
|
+
* without bubblewrap installed, CI containers without user namespaces).
|
|
12
|
+
*/
|
|
13
|
+
const sandboxingAvailable = (() => {
|
|
14
|
+
if (process.platform === "darwin") {
|
|
15
|
+
return true; // sandbox-exec ships with macOS
|
|
16
|
+
}
|
|
17
|
+
if (process.platform === "linux") {
|
|
18
|
+
try {
|
|
19
|
+
// srt needs bwrap (with permission to create user namespaces — on
|
|
20
|
+
// Ubuntu 24.04+ that requires an AppArmor profile or
|
|
21
|
+
// kernel.apparmor_restrict_unprivileged_userns=0), socat for its
|
|
22
|
+
// network proxy bridge, and ripgrep.
|
|
23
|
+
execFileSync("bwrap", ["--ro-bind", "/", "/", "true"], {
|
|
24
|
+
stdio: "ignore",
|
|
25
|
+
timeout: 10000,
|
|
26
|
+
});
|
|
27
|
+
execFileSync("socat", ["-V"], { stdio: "ignore", timeout: 10000 });
|
|
28
|
+
execFileSync("rg", ["--version"], { stdio: "ignore", timeout: 10000 });
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
})();
|
|
37
|
+
/** Run a shell script through the sandbox's primary exec() path. exec()
|
|
38
|
+
* throws on non-zero exit, which is exactly the signal these tests assert. */
|
|
39
|
+
async function run(handle, script) {
|
|
40
|
+
const chunks = [];
|
|
41
|
+
try {
|
|
42
|
+
for await (const chunk of handle.exec(["sh", "-c", script])) {
|
|
43
|
+
chunks.push(new TextDecoder().decode(chunk));
|
|
44
|
+
}
|
|
45
|
+
return { ok: true, output: chunks.join("") };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { ok: false, output: chunks.join("") };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
describe.skipIf(!sandboxingAvailable)("SrtSandbox (real isolation)", () => {
|
|
52
|
+
let workdir;
|
|
53
|
+
let outsideDir;
|
|
54
|
+
beforeEach(async () => {
|
|
55
|
+
workdir = await fs.mkdtemp(path.join(os.tmpdir(), "srt-sandbox-wd-"));
|
|
56
|
+
// A directory the policy does NOT allow writes to. It must live outside
|
|
57
|
+
// the OS temp dir (which SrtSandbox always allows) — and the repo itself
|
|
58
|
+
// can sit under the temp dir (e.g. a /tmp git worktree), so anchor to
|
|
59
|
+
// the user's home rather than process.cwd(). Removed in afterEach.
|
|
60
|
+
outsideDir = await fs.mkdtemp(path.join(os.homedir(), ".srt-test-outside-"));
|
|
61
|
+
});
|
|
62
|
+
afterEach(async () => {
|
|
63
|
+
await fs.rm(workdir, { recursive: true, force: true }).catch(() => { });
|
|
64
|
+
await fs.rm(outsideDir, { recursive: true, force: true }).catch(() => { });
|
|
65
|
+
});
|
|
66
|
+
it("runs commands and allows writes inside the workdir", async () => {
|
|
67
|
+
const sandbox = new SrtSandbox({ workdir });
|
|
68
|
+
const handle = await sandbox.attach();
|
|
69
|
+
const result = await run(handle, "echo sandboxed-ok > inside.txt && cat inside.txt");
|
|
70
|
+
expect(result.ok).toBe(true);
|
|
71
|
+
expect(result.output).toContain("sandboxed-ok");
|
|
72
|
+
// The write really landed on the host filesystem.
|
|
73
|
+
const content = await fs.readFile(path.join(workdir, "inside.txt"), "utf8");
|
|
74
|
+
expect(content).toContain("sandboxed-ok");
|
|
75
|
+
}, 120000);
|
|
76
|
+
it("denies writes outside the allowed paths", async () => {
|
|
77
|
+
const sandbox = new SrtSandbox({ workdir });
|
|
78
|
+
const handle = await sandbox.attach();
|
|
79
|
+
const target = path.join(outsideDir, "escape.txt");
|
|
80
|
+
const result = await run(handle, `touch ${target}`);
|
|
81
|
+
expect(result.ok).toBe(false);
|
|
82
|
+
await expect(fs.access(target)).rejects.toThrow();
|
|
83
|
+
}, 120000);
|
|
84
|
+
it("denies reads of paths listed in denyRead", async () => {
|
|
85
|
+
const secretDir = path.join(outsideDir, "secrets");
|
|
86
|
+
await fs.mkdir(secretDir, { recursive: true });
|
|
87
|
+
await fs.writeFile(path.join(secretDir, "key.txt"), "top-secret");
|
|
88
|
+
const sandbox = new SrtSandbox({
|
|
89
|
+
workdir,
|
|
90
|
+
isolation: { denyRead: [secretDir] },
|
|
91
|
+
});
|
|
92
|
+
const handle = await sandbox.attach();
|
|
93
|
+
const result = await run(handle, `cat ${secretDir}/key.txt`);
|
|
94
|
+
expect(result.ok).toBe(false);
|
|
95
|
+
expect(result.output).not.toContain("top-secret");
|
|
96
|
+
}, 120000);
|
|
97
|
+
it("blocks network access by default", async () => {
|
|
98
|
+
const sandbox = new SrtSandbox({ workdir });
|
|
99
|
+
const handle = await sandbox.attach();
|
|
100
|
+
// 5s timeout so a (wrongly) reachable network still fails fast.
|
|
101
|
+
const result = await run(handle, "curl -sS --max-time 5 https://example.com");
|
|
102
|
+
expect(result.ok).toBe(false);
|
|
103
|
+
}, 120000);
|
|
104
|
+
it("sandboxed process cannot rewrite its own policy file", async () => {
|
|
105
|
+
const sandbox = new SrtSandbox({ workdir });
|
|
106
|
+
const handle = await sandbox.attach();
|
|
107
|
+
// Locate the generated settings file via the wrapper command itself:
|
|
108
|
+
// it is passed as `--settings <path>` and lives under an srt-sandbox-*
|
|
109
|
+
// temp dir that the policy denies writes to.
|
|
110
|
+
const probe = await run(handle, "echo policy-probe");
|
|
111
|
+
expect(probe.ok).toBe(true);
|
|
112
|
+
const dirs = await fs.readdir(os.tmpdir());
|
|
113
|
+
const policyDirs = dirs.filter((d) => d.startsWith("srt-sandbox-"));
|
|
114
|
+
expect(policyDirs.length).toBeGreaterThan(0);
|
|
115
|
+
let deniedSomewhere = false;
|
|
116
|
+
for (const dir of policyDirs) {
|
|
117
|
+
const settingsPath = path.join(os.tmpdir(), dir, "srt-settings.json");
|
|
118
|
+
const result = await run(handle, `echo '{}' > ${settingsPath}`);
|
|
119
|
+
if (!result.ok) {
|
|
120
|
+
deniedSomewhere = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
expect(deniedSomewhere).toBe(true);
|
|
124
|
+
}, 120000);
|
|
125
|
+
it("abort/timeout leaves no orphaned grandchild process (PID namespace)", async () => {
|
|
126
|
+
// LocalMachine (no isolation) needs its own process-group kill logic to
|
|
127
|
+
// catch backgrounded grandchildren on abort/timeout (see
|
|
128
|
+
// sandbox-local's local-machine.test.ts). SrtSandbox doesn't need that
|
|
129
|
+
// extra mechanism: srt passes bwrap `--unshare-pid`, so the kernel tears
|
|
130
|
+
// down every process in that PID namespace the moment its init process
|
|
131
|
+
// dies — this test locks that guarantee in so a future srt/bwrap flag
|
|
132
|
+
// change can't silently regress it.
|
|
133
|
+
const sandbox = new SrtSandbox({ workdir });
|
|
134
|
+
const handle = await sandbox.attach();
|
|
135
|
+
const marker = `srt-orphan-test-${process.pid}-${Date.now()}`;
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
const consume = (async () => {
|
|
138
|
+
try {
|
|
139
|
+
for await (const chunk of handle.exec([
|
|
140
|
+
"bash",
|
|
141
|
+
"-c",
|
|
142
|
+
`(exec -a ${marker} sleep 60) & echo started; sleep 60`,
|
|
143
|
+
], { signal: controller.signal })) {
|
|
144
|
+
void chunk;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Expected: exec() rejects once aborted.
|
|
149
|
+
}
|
|
150
|
+
})();
|
|
151
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
152
|
+
controller.abort();
|
|
153
|
+
await consume;
|
|
154
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
155
|
+
let survived = false;
|
|
156
|
+
try {
|
|
157
|
+
execSync(`pgrep -f -x "${marker} 60"`, { stdio: "ignore" });
|
|
158
|
+
survived = true;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
survived = false; // pgrep exits non-zero when nothing matches
|
|
162
|
+
}
|
|
163
|
+
expect(survived).toBe(false);
|
|
164
|
+
}, 120000);
|
|
165
|
+
it("destroy() removes the generated srt-settings temp directory", async () => {
|
|
166
|
+
// Regression: ensureSettingsFile() mkdtemp's a `srt-sandbox-*` dir under
|
|
167
|
+
// the OS temp dir for the generated policy file, but nothing removed it
|
|
168
|
+
// — every SrtSandbox instance that ran a command leaked one directory
|
|
169
|
+
// for the life of the host. Confirmed for real: a debugging session
|
|
170
|
+
// running this suite repeatedly had accumulated 80+ leaked directories
|
|
171
|
+
// in /tmp before this fix.
|
|
172
|
+
const before = new Set((await fs.readdir(os.tmpdir())).filter((d) => d.startsWith("srt-sandbox-")));
|
|
173
|
+
const sandbox = new SrtSandbox({ workdir });
|
|
174
|
+
const handle = await sandbox.attach();
|
|
175
|
+
const result = await run(handle, "echo hi");
|
|
176
|
+
expect(result.ok).toBe(true);
|
|
177
|
+
const afterRun = new Set((await fs.readdir(os.tmpdir())).filter((d) => d.startsWith("srt-sandbox-")));
|
|
178
|
+
const created = [...afterRun].filter((d) => !before.has(d));
|
|
179
|
+
expect(created.length).toBeGreaterThan(0);
|
|
180
|
+
await handle.destroy();
|
|
181
|
+
const afterDestroy = new Set((await fs.readdir(os.tmpdir())).filter((d) => d.startsWith("srt-sandbox-")));
|
|
182
|
+
for (const dir of created) {
|
|
183
|
+
expect(afterDestroy.has(dir)).toBe(false);
|
|
184
|
+
}
|
|
185
|
+
}, 120000);
|
|
186
|
+
});
|
|
187
|
+
describe("buildInstallHint", () => {
|
|
188
|
+
it("suggests installing all dependencies named in srt's message", () => {
|
|
189
|
+
const hint = buildInstallHint("Sandbox dependencies not available: ripgrep (rg) not found, bubblewrap (bwrap) not installed, socat not installed");
|
|
190
|
+
expect(hint).toContain("apt-get install -y ripgrep bubblewrap socat");
|
|
191
|
+
expect(hint).toContain("brew install ripgrep socat");
|
|
192
|
+
expect(hint).toContain("userns");
|
|
193
|
+
});
|
|
194
|
+
it("only mentions what the message actually names as missing", () => {
|
|
195
|
+
const hint = buildInstallHint("Sandbox dependencies not available: socat not installed");
|
|
196
|
+
expect(hint).toContain("apt-get install -y socat");
|
|
197
|
+
expect(hint).not.toContain("ripgrep");
|
|
198
|
+
expect(hint).not.toContain("bubblewrap");
|
|
199
|
+
});
|
|
200
|
+
it("returns an empty string for a message naming no known dependency", () => {
|
|
201
|
+
expect(buildInstallHint("some unrelated failure")).toBe("");
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
/**
|
|
205
|
+
* Preflight-check tests use a fake `srtCommand` (a tiny throwaway script)
|
|
206
|
+
* instead of the real srt/bwrap wrapper, so they exercise attach()'s
|
|
207
|
+
* fail-fast behavior deterministically on any platform/CI environment —
|
|
208
|
+
* unlike the "real isolation" suite above, they don't need bubblewrap,
|
|
209
|
+
* socat, or ripgrep actually installed.
|
|
210
|
+
*/
|
|
211
|
+
describe("SrtSandbox preflight check (fake wrapper)", () => {
|
|
212
|
+
let workdir;
|
|
213
|
+
beforeEach(async () => {
|
|
214
|
+
workdir = await fs.mkdtemp(path.join(os.tmpdir(), "srt-preflight-wd-"));
|
|
215
|
+
});
|
|
216
|
+
afterEach(async () => {
|
|
217
|
+
await fs.rm(workdir, { recursive: true, force: true }).catch(() => { });
|
|
218
|
+
});
|
|
219
|
+
async function writeFakeSrt(script) {
|
|
220
|
+
const scriptPath = path.join(workdir, "fake-srt.cjs");
|
|
221
|
+
await fs.writeFile(scriptPath, script);
|
|
222
|
+
return [process.execPath, scriptPath];
|
|
223
|
+
}
|
|
224
|
+
it("attach() rejects immediately with an actionable message on a missing-dependency signature", async () => {
|
|
225
|
+
const srtCommand = await writeFakeSrt('console.error("Error: Sandbox dependencies not available: ripgrep (rg) not found, bubblewrap (bwrap) not installed, socat not installed"); process.exit(1);');
|
|
226
|
+
const sandbox = new SrtSandbox({ workdir, isolation: { srtCommand } });
|
|
227
|
+
await expect(sandbox.attach()).rejects.toThrow(/ripgrep/);
|
|
228
|
+
});
|
|
229
|
+
it("does not mislabel an unrelated startup failure as a missing dependency", async () => {
|
|
230
|
+
const srtCommand = await writeFakeSrt('console.error("Error: something else entirely broke"); process.exit(1);');
|
|
231
|
+
const sandbox = new SrtSandbox({ workdir, isolation: { srtCommand } });
|
|
232
|
+
await expect(sandbox.attach()).rejects.toThrow(/failed its startup preflight check/);
|
|
233
|
+
await expect(new SrtSandbox({ workdir, isolation: { srtCommand } }).attach()).rejects.not.toThrow(/apt-get install/);
|
|
234
|
+
});
|
|
235
|
+
it("attach() succeeds once the preflight probe passes", async () => {
|
|
236
|
+
const srtCommand = await writeFakeSrt("process.exit(0);");
|
|
237
|
+
const sandbox = new SrtSandbox({ workdir, isolation: { srtCommand } });
|
|
238
|
+
const handle = await sandbox.attach();
|
|
239
|
+
expect(handle).toBeDefined();
|
|
240
|
+
});
|
|
241
|
+
it("does not re-run the preflight probe on a second attach() (already attached)", async () => {
|
|
242
|
+
const scriptPath = path.join(workdir, "counting-srt.cjs");
|
|
243
|
+
const counterPath = path.join(workdir, "count.txt");
|
|
244
|
+
await fs.writeFile(counterPath, "0");
|
|
245
|
+
// Every invocation bumps a counter file — including the real payload
|
|
246
|
+
// args exec()/runCommand() would pass — but this test only calls
|
|
247
|
+
// attach() twice, so any increment beyond 1 means the probe re-ran.
|
|
248
|
+
await fs.writeFile(scriptPath, [
|
|
249
|
+
'const fs = require("fs");',
|
|
250
|
+
`const p = ${JSON.stringify(counterPath)};`,
|
|
251
|
+
"fs.writeFileSync(p, String(Number(fs.readFileSync(p, 'utf8')) + 1));",
|
|
252
|
+
"process.exit(0);",
|
|
253
|
+
].join("\n"));
|
|
254
|
+
const sandbox = new SrtSandbox({
|
|
255
|
+
workdir,
|
|
256
|
+
isolation: { srtCommand: [process.execPath, scriptPath] },
|
|
257
|
+
});
|
|
258
|
+
await sandbox.attach();
|
|
259
|
+
expect(Number(await fs.readFile(counterPath, "utf8"))).toBe(1);
|
|
260
|
+
await sandbox.attach();
|
|
261
|
+
expect(Number(await fs.readFile(counterPath, "utf8"))).toBe(1);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
//# sourceMappingURL=srt-sandbox.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"srt-sandbox.test.js","sourceRoot":"","sources":["../../src/__tests__/srt-sandbox.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { SandboxHandle } from "@bunny-agent/manager";
|
|
2
|
+
import { LocalMachine, type LocalMachineOptions } from "@bunny-agent/sandbox-local";
|
|
3
|
+
/**
|
|
4
|
+
* Builds an actionable "how to fix this" hint appended to srt's own missing-
|
|
5
|
+
* dependency message. Only mentions dependencies srt's message actually
|
|
6
|
+
* named (never asserts what's needed on a platform we haven't confirmed).
|
|
7
|
+
* Exported for direct unit testing — not part of the package's public API.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildInstallHint(srtMessage: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Isolation policy for {@link SrtSandbox}, mapped onto
|
|
12
|
+
* `@anthropic-ai/sandbox-runtime` (srt) settings.
|
|
13
|
+
*
|
|
14
|
+
* Defaults are deny-by-default where srt is deny-by-default:
|
|
15
|
+
* - Network: fully blocked unless domains are listed in `allowedDomains`.
|
|
16
|
+
* - Filesystem writes: only the sandbox workdir and the OS temp dir are
|
|
17
|
+
* writable unless more paths are listed in `allowWrite`.
|
|
18
|
+
* - Filesystem reads: allowed except paths listed in `denyRead`.
|
|
19
|
+
*/
|
|
20
|
+
export interface SrtIsolationOptions {
|
|
21
|
+
/** Domains the sandboxed process may reach (supports `*.example.com`).
|
|
22
|
+
* Default: `[]` — no network access. */
|
|
23
|
+
allowedDomains?: string[];
|
|
24
|
+
/** Domains to always block (takes precedence over `allowedDomains`). */
|
|
25
|
+
deniedDomains?: string[];
|
|
26
|
+
/** Paths the sandboxed process may NOT read (e.g. `~/.ssh`). */
|
|
27
|
+
denyRead?: string[];
|
|
28
|
+
/** Extra writable paths, in addition to the workdir and the OS temp dir.
|
|
29
|
+
* Runners that write to the home directory (npm cache, session stores)
|
|
30
|
+
* need those paths listed here — e.g. `["~/.npm", "~/.bunny-agent"]`. */
|
|
31
|
+
allowWrite?: string[];
|
|
32
|
+
/** Paths to deny writes to even inside allowed paths. */
|
|
33
|
+
denyWrite?: string[];
|
|
34
|
+
/** Allow binding local ports (dev servers inside the sandbox). Default false. */
|
|
35
|
+
allowLocalBinding?: boolean;
|
|
36
|
+
/** Disable Unix-socket blocking (Linux: seccomp; macOS: Seatbelt). Default false. */
|
|
37
|
+
allowAllUnixSockets?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Bring-your-own srt settings file. When set, it is passed to srt as-is and
|
|
40
|
+
* every other option in this object is ignored.
|
|
41
|
+
*/
|
|
42
|
+
settingsPath?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Override the wrapper invocation (argv prefix). Defaults to running the
|
|
45
|
+
* `srt` CLI bundled with `@anthropic-ai/sandbox-runtime` via the current
|
|
46
|
+
* Node executable.
|
|
47
|
+
*/
|
|
48
|
+
srtCommand?: string[];
|
|
49
|
+
}
|
|
50
|
+
export interface SrtSandboxOptions extends LocalMachineOptions {
|
|
51
|
+
isolation?: SrtIsolationOptions;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A locally-isolated sandbox: same lifecycle and API as {@link LocalMachine},
|
|
55
|
+
* but every command is wrapped with Anthropic's sandbox runtime
|
|
56
|
+
* (`@anthropic-ai/sandbox-runtime`, the sandbox used by Claude Code), which
|
|
57
|
+
* enforces OS-level boundaries:
|
|
58
|
+
*
|
|
59
|
+
* - Linux: bubblewrap + network-namespace isolation (traffic must go through
|
|
60
|
+
* srt's host-side proxies)
|
|
61
|
+
* - macOS: `sandbox-exec` with a generated Seatbelt profile
|
|
62
|
+
* - Windows (alpha): a dedicated `srt-sandbox` user account + WFP egress fence
|
|
63
|
+
*
|
|
64
|
+
* The policy is allow-only for network and writes: by default the process
|
|
65
|
+
* can read the filesystem (minus `denyRead`), write only to the workdir and
|
|
66
|
+
* the OS temp dir, and reach no network at all.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* import { BunnyAgent } from "@bunny-agent/manager";
|
|
71
|
+
* import { SrtSandbox } from "@bunny-agent/sandbox-srt";
|
|
72
|
+
*
|
|
73
|
+
* const sandbox = new SrtSandbox({
|
|
74
|
+
* workdir: "/tmp/my-sandbox",
|
|
75
|
+
* isolation: {
|
|
76
|
+
* allowedDomains: ["api.anthropic.com", "*.npmjs.org"],
|
|
77
|
+
* denyRead: ["~/.ssh", "~/.aws"],
|
|
78
|
+
* allowWrite: ["~/.npm"],
|
|
79
|
+
* },
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* const handle = await sandbox.attach();
|
|
83
|
+
* // Runs inside the OS sandbox; writing outside the workdir fails.
|
|
84
|
+
* const result = await handle.runCommand("id && touch out.txt");
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export declare class SrtSandbox extends LocalMachine {
|
|
88
|
+
protected readonly label: string;
|
|
89
|
+
private readonly isolation;
|
|
90
|
+
/** Generated settings file path; created lazily, stable per instance. */
|
|
91
|
+
private settingsFilePromise;
|
|
92
|
+
/** The mkdtemp'd directory holding the generated settings file — tracked
|
|
93
|
+
* separately so onDestroy() can remove it without re-deriving it from
|
|
94
|
+
* the (possibly not-yet-created) settings path. */
|
|
95
|
+
private generatedSettingsDir;
|
|
96
|
+
constructor(options?: SrtSandboxOptions);
|
|
97
|
+
/**
|
|
98
|
+
* Runs a cheap srt-wrapped no-op before doing any real work, so a missing
|
|
99
|
+
* platform dependency (bubblewrap, socat, ripgrep, ...) or an otherwise
|
|
100
|
+
* unusable sandbox surfaces immediately with an actionable message —
|
|
101
|
+
* instead of being discovered deep inside the first real `exec()` call,
|
|
102
|
+
* where it previously arrived as a generic "Command failed (exit 1)"
|
|
103
|
+
* with srt's raw stderr buried in the trace.
|
|
104
|
+
*/
|
|
105
|
+
attach(): Promise<SandboxHandle>;
|
|
106
|
+
private preflightCheck;
|
|
107
|
+
protected transformCommand(command: string[]): Promise<string[]>;
|
|
108
|
+
/**
|
|
109
|
+
* Write the srt settings file derived from {@link SrtIsolationOptions}.
|
|
110
|
+
* The file lives under the OS temp dir, which is inside the sandbox's
|
|
111
|
+
* writable paths — so the policy explicitly denies writes to its own
|
|
112
|
+
* directory (`denyWrite` takes precedence over `allowWrite` in srt): a
|
|
113
|
+
* sandboxed process cannot edit its own policy for the next run.
|
|
114
|
+
*/
|
|
115
|
+
private ensureSettingsFile;
|
|
116
|
+
/**
|
|
117
|
+
* Removes the generated `srt-sandbox-*` settings directory, if one was
|
|
118
|
+
* created (bring-your-own `settingsPath` never triggers this). Without
|
|
119
|
+
* this, every SrtSandbox instance that actually ran a command leaked one
|
|
120
|
+
* temp directory under the OS temp dir for the lifetime of the host —
|
|
121
|
+
* across many agent runs (each gets its own SrtSandbox instance) these
|
|
122
|
+
* accumulate indefinitely.
|
|
123
|
+
*/
|
|
124
|
+
protected onDestroy(): Promise<void>;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=srt-sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"srt-sandbox.d.ts","sourceRoot":"","sources":["../src/srt-sandbox.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AA6BpC;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAkC3D;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC;6CACyC;IACzC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;8EAE0E;IAC1E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,qFAAqF;IACrF,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,SAAS,CAAC,EAAE,mBAAmB,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,UAAW,SAAQ,YAAY;IAC1C,mBAA4B,KAAK,EAAE,MAAM,CAAgB;IAEzD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,yEAAyE;IACzE,OAAO,CAAC,mBAAmB,CAAgC;IAC3D;;wDAEoD;IACpD,OAAO,CAAC,oBAAoB,CAAuB;gBAEvC,OAAO,GAAE,iBAAsB;IAK3C;;;;;;;OAOG;IACY,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;YAOjC,cAAc;cA6CH,gBAAgB,CACvC,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC;IAUpB;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAgC1B;;;;;;;OAOG;cACsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAKpD"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { LocalMachine, } from "@bunny-agent/sandbox-local";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
/** The exact prefix srt uses when a required platform binary (bubblewrap,
|
|
9
|
+
* socat, ripgrep, ...) is missing — confirmed against
|
|
10
|
+
* `@anthropic-ai/sandbox-runtime@0.0.65` by a live repro with those
|
|
11
|
+
* binaries hidden from PATH. Matching on this signature (rather than
|
|
12
|
+
* guessing at srt's exact dependency list ourselves, which could drift out
|
|
13
|
+
* of sync with a future srt version) lets SrtSandbox tell "you're missing
|
|
14
|
+
* a dependency" apart from "the sandbox failed to start for some other
|
|
15
|
+
* reason" without duplicating srt's own platform-requirements logic. */
|
|
16
|
+
const MISSING_DEPS_SIGNATURE = "Sandbox dependencies not available";
|
|
17
|
+
/** Package names to suggest installing, keyed by the dependency name
|
|
18
|
+
* substring srt's own error message uses (see MISSING_DEPS_SIGNATURE's
|
|
19
|
+
* doc comment — these are read out of srt's message, not asserted
|
|
20
|
+
* independently, so an unlisted dependency just gets no extra hint
|
|
21
|
+
* rather than a wrong one). `bubblewrap` has no `brew` entry: it's a
|
|
22
|
+
* Linux-only sandboxing primitive, macOS uses Seatbelt instead. */
|
|
23
|
+
const DEPENDENCY_PACKAGE_NAMES = {
|
|
24
|
+
ripgrep: { apt: "ripgrep", brew: "ripgrep" },
|
|
25
|
+
bubblewrap: { apt: "bubblewrap" },
|
|
26
|
+
socat: { apt: "socat", brew: "socat" },
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Builds an actionable "how to fix this" hint appended to srt's own missing-
|
|
30
|
+
* dependency message. Only mentions dependencies srt's message actually
|
|
31
|
+
* named (never asserts what's needed on a platform we haven't confirmed).
|
|
32
|
+
* Exported for direct unit testing — not part of the package's public API.
|
|
33
|
+
*/
|
|
34
|
+
export function buildInstallHint(srtMessage) {
|
|
35
|
+
const lowerMessage = srtMessage.toLowerCase();
|
|
36
|
+
const missing = Object.keys(DEPENDENCY_PACKAGE_NAMES).filter((name) => lowerMessage.includes(name));
|
|
37
|
+
if (missing.length === 0) {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
const aptPackages = missing
|
|
41
|
+
.map((name) => DEPENDENCY_PACKAGE_NAMES[name].apt)
|
|
42
|
+
.filter((pkg) => Boolean(pkg));
|
|
43
|
+
const brewPackages = missing
|
|
44
|
+
.map((name) => DEPENDENCY_PACKAGE_NAMES[name].brew)
|
|
45
|
+
.filter((pkg) => Boolean(pkg));
|
|
46
|
+
const lines = ["", "To fix:"];
|
|
47
|
+
if (aptPackages.length > 0) {
|
|
48
|
+
lines.push(` Debian/Ubuntu: sudo apt-get install -y ${aptPackages.join(" ")}`);
|
|
49
|
+
}
|
|
50
|
+
if (brewPackages.length > 0) {
|
|
51
|
+
lines.push(` macOS: brew install ${brewPackages.join(" ")}`);
|
|
52
|
+
}
|
|
53
|
+
if (missing.includes("bubblewrap")) {
|
|
54
|
+
lines.push(" Ubuntu 24.04+ additionally restricts unprivileged user namespaces by " +
|
|
55
|
+
"default, which bubblewrap needs — either " +
|
|
56
|
+
"`sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0` or grant " +
|
|
57
|
+
"an AppArmor profile permitting bwrap's `userns` (see docs/SANDBOX_ADAPTERS.md).");
|
|
58
|
+
}
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A locally-isolated sandbox: same lifecycle and API as {@link LocalMachine},
|
|
63
|
+
* but every command is wrapped with Anthropic's sandbox runtime
|
|
64
|
+
* (`@anthropic-ai/sandbox-runtime`, the sandbox used by Claude Code), which
|
|
65
|
+
* enforces OS-level boundaries:
|
|
66
|
+
*
|
|
67
|
+
* - Linux: bubblewrap + network-namespace isolation (traffic must go through
|
|
68
|
+
* srt's host-side proxies)
|
|
69
|
+
* - macOS: `sandbox-exec` with a generated Seatbelt profile
|
|
70
|
+
* - Windows (alpha): a dedicated `srt-sandbox` user account + WFP egress fence
|
|
71
|
+
*
|
|
72
|
+
* The policy is allow-only for network and writes: by default the process
|
|
73
|
+
* can read the filesystem (minus `denyRead`), write only to the workdir and
|
|
74
|
+
* the OS temp dir, and reach no network at all.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* import { BunnyAgent } from "@bunny-agent/manager";
|
|
79
|
+
* import { SrtSandbox } from "@bunny-agent/sandbox-srt";
|
|
80
|
+
*
|
|
81
|
+
* const sandbox = new SrtSandbox({
|
|
82
|
+
* workdir: "/tmp/my-sandbox",
|
|
83
|
+
* isolation: {
|
|
84
|
+
* allowedDomains: ["api.anthropic.com", "*.npmjs.org"],
|
|
85
|
+
* denyRead: ["~/.ssh", "~/.aws"],
|
|
86
|
+
* allowWrite: ["~/.npm"],
|
|
87
|
+
* },
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* const handle = await sandbox.attach();
|
|
91
|
+
* // Runs inside the OS sandbox; writing outside the workdir fails.
|
|
92
|
+
* const result = await handle.runCommand("id && touch out.txt");
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export class SrtSandbox extends LocalMachine {
|
|
96
|
+
label = "SrtSandbox";
|
|
97
|
+
isolation;
|
|
98
|
+
/** Generated settings file path; created lazily, stable per instance. */
|
|
99
|
+
settingsFilePromise = null;
|
|
100
|
+
/** The mkdtemp'd directory holding the generated settings file — tracked
|
|
101
|
+
* separately so onDestroy() can remove it without re-deriving it from
|
|
102
|
+
* the (possibly not-yet-created) settings path. */
|
|
103
|
+
generatedSettingsDir = null;
|
|
104
|
+
constructor(options = {}) {
|
|
105
|
+
super(options);
|
|
106
|
+
this.isolation = options.isolation ?? {};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Runs a cheap srt-wrapped no-op before doing any real work, so a missing
|
|
110
|
+
* platform dependency (bubblewrap, socat, ripgrep, ...) or an otherwise
|
|
111
|
+
* unusable sandbox surfaces immediately with an actionable message —
|
|
112
|
+
* instead of being discovered deep inside the first real `exec()` call,
|
|
113
|
+
* where it previously arrived as a generic "Command failed (exit 1)"
|
|
114
|
+
* with srt's raw stderr buried in the trace.
|
|
115
|
+
*/
|
|
116
|
+
async attach() {
|
|
117
|
+
if (!this.getHandle()) {
|
|
118
|
+
await this.preflightCheck();
|
|
119
|
+
}
|
|
120
|
+
return super.attach();
|
|
121
|
+
}
|
|
122
|
+
async preflightCheck() {
|
|
123
|
+
const settingsPath = this.isolation.settingsPath ?? (await this.ensureSettingsFile());
|
|
124
|
+
const srtCommand = this.isolation.srtCommand ?? [
|
|
125
|
+
process.execPath,
|
|
126
|
+
require.resolve("@anthropic-ai/sandbox-runtime/dist/cli.js"),
|
|
127
|
+
];
|
|
128
|
+
// A harmless payload: node itself is guaranteed present (we're running
|
|
129
|
+
// inside it right now), so the probe doesn't depend on `true`/`echo`
|
|
130
|
+
// existing, and it touches neither the filesystem nor the network.
|
|
131
|
+
const [cmd, ...args] = [
|
|
132
|
+
...srtCommand,
|
|
133
|
+
"--settings",
|
|
134
|
+
settingsPath,
|
|
135
|
+
process.execPath,
|
|
136
|
+
"-e",
|
|
137
|
+
"process.exit(0)",
|
|
138
|
+
];
|
|
139
|
+
await new Promise((resolve, reject) => {
|
|
140
|
+
execFile(cmd, args, { timeout: 15000 }, (error, _stdout, stderr) => {
|
|
141
|
+
if (!error) {
|
|
142
|
+
resolve();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const message = (stderr || error.message).trim();
|
|
146
|
+
if (message.includes(MISSING_DEPS_SIGNATURE)) {
|
|
147
|
+
reject(new Error(`SrtSandbox cannot start: ${message}${buildInstallHint(message)}`));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
reject(new Error("SrtSandbox failed its startup preflight check (this usually means " +
|
|
151
|
+
"the sandboxing runtime — bubblewrap on Linux, Seatbelt on macOS — " +
|
|
152
|
+
`can't run in this environment): ${message}`));
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async transformCommand(command) {
|
|
157
|
+
const settingsPath = this.isolation.settingsPath ?? (await this.ensureSettingsFile());
|
|
158
|
+
const srtCommand = this.isolation.srtCommand ?? [
|
|
159
|
+
process.execPath,
|
|
160
|
+
require.resolve("@anthropic-ai/sandbox-runtime/dist/cli.js"),
|
|
161
|
+
];
|
|
162
|
+
return [...srtCommand, "--settings", settingsPath, ...command];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Write the srt settings file derived from {@link SrtIsolationOptions}.
|
|
166
|
+
* The file lives under the OS temp dir, which is inside the sandbox's
|
|
167
|
+
* writable paths — so the policy explicitly denies writes to its own
|
|
168
|
+
* directory (`denyWrite` takes precedence over `allowWrite` in srt): a
|
|
169
|
+
* sandboxed process cannot edit its own policy for the next run.
|
|
170
|
+
*/
|
|
171
|
+
ensureSettingsFile() {
|
|
172
|
+
this.settingsFilePromise ??= (async () => {
|
|
173
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "srt-sandbox-"));
|
|
174
|
+
this.generatedSettingsDir = dir;
|
|
175
|
+
const settingsPath = path.join(dir, "srt-settings.json");
|
|
176
|
+
const settings = {
|
|
177
|
+
network: {
|
|
178
|
+
allowedDomains: this.isolation.allowedDomains ?? [],
|
|
179
|
+
deniedDomains: this.isolation.deniedDomains ?? [],
|
|
180
|
+
allowLocalBinding: this.isolation.allowLocalBinding ?? false,
|
|
181
|
+
allowAllUnixSockets: this.isolation.allowAllUnixSockets ?? false,
|
|
182
|
+
},
|
|
183
|
+
filesystem: {
|
|
184
|
+
denyRead: this.isolation.denyRead ?? [],
|
|
185
|
+
allowWrite: [
|
|
186
|
+
this.getWorkdir(),
|
|
187
|
+
os.tmpdir(),
|
|
188
|
+
...(this.isolation.allowWrite ?? []),
|
|
189
|
+
],
|
|
190
|
+
// The policy file itself (and its directory) must never be
|
|
191
|
+
// writable from inside the sandbox, even though it lives under
|
|
192
|
+
// the OS temp dir.
|
|
193
|
+
denyWrite: [dir, ...(this.isolation.denyWrite ?? [])],
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
197
|
+
console.log(`[${this.label}] Wrote srt settings: ${settingsPath}`);
|
|
198
|
+
return settingsPath;
|
|
199
|
+
})();
|
|
200
|
+
return this.settingsFilePromise;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Removes the generated `srt-sandbox-*` settings directory, if one was
|
|
204
|
+
* created (bring-your-own `settingsPath` never triggers this). Without
|
|
205
|
+
* this, every SrtSandbox instance that actually ran a command leaked one
|
|
206
|
+
* temp directory under the OS temp dir for the lifetime of the host —
|
|
207
|
+
* across many agent runs (each gets its own SrtSandbox instance) these
|
|
208
|
+
* accumulate indefinitely.
|
|
209
|
+
*/
|
|
210
|
+
async onDestroy() {
|
|
211
|
+
if (this.generatedSettingsDir) {
|
|
212
|
+
await fs.rm(this.generatedSettingsDir, { recursive: true, force: true });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=srt-sandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"srt-sandbox.js","sourceRoot":"","sources":["../src/srt-sandbox.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bunny-agent/sandbox-srt",
|
|
3
|
+
"version": "0.9.52-beta.1",
|
|
4
|
+
"description": "Locally-isolated sandbox adapter for BunnyAgent — wraps commands with Anthropic's sandbox runtime (srt)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/vikadata/bunny-agent.git",
|
|
21
|
+
"directory": "packages/sandbox-srt"
|
|
22
|
+
},
|
|
23
|
+
"license": "Apache-2.0",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20.11.0"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"sandbox",
|
|
32
|
+
"srt",
|
|
33
|
+
"isolation",
|
|
34
|
+
"bubblewrap",
|
|
35
|
+
"seatbelt",
|
|
36
|
+
"bunny-agent",
|
|
37
|
+
"adapter"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@anthropic-ai/sandbox-runtime": "^0.0.65",
|
|
41
|
+
"@bunny-agent/manager": "0.9.52-beta.1",
|
|
42
|
+
"@bunny-agent/sandbox-local": "0.9.52-beta.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.10.0",
|
|
46
|
+
"typescript": "^5.3.0",
|
|
47
|
+
"vitest": "^1.6.1"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc",
|
|
51
|
+
"dev": "tsc --watch",
|
|
52
|
+
"clean": "rm -rf dist",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"lint": "echo 'no lint configured'",
|
|
55
|
+
"test": "vitest run --passWithNoTests"
|
|
56
|
+
}
|
|
57
|
+
}
|