@agentsh/secure-sandbox 0.1.0
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 +198 -0
- package/dist/adapters/blaxel.d.ts +5 -0
- package/dist/adapters/blaxel.js +9 -0
- package/dist/adapters/blaxel.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +5 -0
- package/dist/adapters/cloudflare.js +9 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/daytona.d.ts +5 -0
- package/dist/adapters/daytona.js +9 -0
- package/dist/adapters/daytona.js.map +1 -0
- package/dist/adapters/e2b.d.ts +5 -0
- package/dist/adapters/e2b.js +9 -0
- package/dist/adapters/e2b.js.map +1 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.js +26 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/vercel.d.ts +5 -0
- package/dist/adapters/vercel.js +8 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/chunk-2P37YGN7.js +52 -0
- package/dist/chunk-2P37YGN7.js.map +1 -0
- package/dist/chunk-45FKFVMC.js +55 -0
- package/dist/chunk-45FKFVMC.js.map +1 -0
- package/dist/chunk-JY5ERJTX.js +49 -0
- package/dist/chunk-JY5ERJTX.js.map +1 -0
- package/dist/chunk-L4KFLVNU.js +33 -0
- package/dist/chunk-L4KFLVNU.js.map +1 -0
- package/dist/chunk-LMN3KM53.js +48 -0
- package/dist/chunk-LMN3KM53.js.map +1 -0
- package/dist/chunk-NWHVZ3DG.js +599 -0
- package/dist/chunk-NWHVZ3DG.js.map +1 -0
- package/dist/chunk-OANLKSOD.js +28 -0
- package/dist/chunk-OANLKSOD.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-UYEAO27E.js +65 -0
- package/dist/chunk-UYEAO27E.js.map +1 -0
- package/dist/esm-7TZRRYDK.js +1375 -0
- package/dist/esm-7TZRRYDK.js.map +1 -0
- package/dist/index-D0UvBOzr.d.ts +463 -0
- package/dist/index-aQ1TVPtG.d.ts +16 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +774 -0
- package/dist/index.js.map +1 -0
- package/dist/policies/index.d.ts +2 -0
- package/dist/policies/index.js +26 -0
- package/dist/policies/index.js.map +1 -0
- package/dist/testing/index.d.ts +13 -0
- package/dist/testing/index.js +32 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/types-BwEbraFo.d.ts +194 -0
- package/package.json +99 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adapters_exports
|
|
3
|
+
} from "./chunk-L4KFLVNU.js";
|
|
4
|
+
import "./chunk-UYEAO27E.js";
|
|
5
|
+
import "./chunk-LMN3KM53.js";
|
|
6
|
+
import "./chunk-45FKFVMC.js";
|
|
7
|
+
import "./chunk-2P37YGN7.js";
|
|
8
|
+
import "./chunk-OANLKSOD.js";
|
|
9
|
+
import "./chunk-JY5ERJTX.js";
|
|
10
|
+
import {
|
|
11
|
+
AgentSHError,
|
|
12
|
+
IncompatibleProviderVersionError,
|
|
13
|
+
IntegrityError,
|
|
14
|
+
MissingPeerDependencyError,
|
|
15
|
+
PolicyValidationError,
|
|
16
|
+
ProvisioningError,
|
|
17
|
+
RuntimeError,
|
|
18
|
+
agentDefault,
|
|
19
|
+
policies_exports,
|
|
20
|
+
serializePolicy,
|
|
21
|
+
systemPolicyYaml,
|
|
22
|
+
validatePolicy
|
|
23
|
+
} from "./chunk-NWHVZ3DG.js";
|
|
24
|
+
import "./chunk-PZ5AY32C.js";
|
|
25
|
+
|
|
26
|
+
// src/core/integrity.ts
|
|
27
|
+
var PINNED_VERSION = "0.15.0";
|
|
28
|
+
var CHECKSUMS = {
|
|
29
|
+
"0.15.0": {
|
|
30
|
+
linux_amd64: "89f7ebbfd75ffd961245ec62b2602fd0cc387740502ac858dbc39c367c5699c5",
|
|
31
|
+
linux_arm64: "3fabbd749f9e98fb9f96ddfc94c389a6868cda7ed3668daa8440c39ceec85f3b"
|
|
32
|
+
},
|
|
33
|
+
"0.14.0": {
|
|
34
|
+
linux_amd64: "2ab8ba0d6637fe1a5badf840c3db197161a6f9865d721ed216029d229b1b9bbc",
|
|
35
|
+
linux_arm64: "929d18dd9fe36e9b2fa830d7ae64b4fb481853e743ade8674fcfcdc73470ed53"
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function getChecksum(version, arch, override) {
|
|
39
|
+
if (override) {
|
|
40
|
+
return override;
|
|
41
|
+
}
|
|
42
|
+
const versionChecksums = CHECKSUMS[version];
|
|
43
|
+
if (versionChecksums && versionChecksums[arch]) {
|
|
44
|
+
return versionChecksums[arch];
|
|
45
|
+
}
|
|
46
|
+
throw new IntegrityError({
|
|
47
|
+
expected: "",
|
|
48
|
+
actual: "",
|
|
49
|
+
message: `No pinned checksum for agentsh v${version}. Provide \`agentshChecksum\` explicitly or use \`skipIntegrityCheck: true\`.`
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function buildVerifyCommand(filePath) {
|
|
53
|
+
return [
|
|
54
|
+
`sha256sum "${filePath}" | awk '{print $1}'`,
|
|
55
|
+
`shasum -a 256 "${filePath}" | awk '{print $1}'`,
|
|
56
|
+
`openssl dgst -sha256 "${filePath}" | awk '{print $NF}'`
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
function binaryUrl(version, arch, overrideUrl) {
|
|
60
|
+
if (overrideUrl) {
|
|
61
|
+
return overrideUrl;
|
|
62
|
+
}
|
|
63
|
+
return `https://github.com/canyonroad/agentsh/releases/download/v${version}/agentsh_${version}_${arch}.tar.gz`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/core/config.ts
|
|
67
|
+
import yaml from "js-yaml";
|
|
68
|
+
var defaultThreatFeeds = {
|
|
69
|
+
action: "deny",
|
|
70
|
+
feeds: [
|
|
71
|
+
{
|
|
72
|
+
name: "urlhaus",
|
|
73
|
+
url: "https://urlhaus.abuse.ch/downloads/hostfile/",
|
|
74
|
+
format: "hostfile",
|
|
75
|
+
refreshInterval: "6h"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "phishing",
|
|
79
|
+
url: "https://raw.githubusercontent.com/mitchellkrogza/Phishing.Database/master/phishing-domains-ACTIVE.txt",
|
|
80
|
+
format: "domain-list",
|
|
81
|
+
refreshInterval: "12h"
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
allowlist: [
|
|
85
|
+
"github.com",
|
|
86
|
+
"*.github.com",
|
|
87
|
+
"registry.npmjs.org",
|
|
88
|
+
"registry.yarnpkg.com",
|
|
89
|
+
"pypi.org",
|
|
90
|
+
"files.pythonhosted.org",
|
|
91
|
+
"crates.io",
|
|
92
|
+
"static.crates.io",
|
|
93
|
+
"index.crates.io",
|
|
94
|
+
"proxy.golang.org",
|
|
95
|
+
"sum.golang.org"
|
|
96
|
+
]
|
|
97
|
+
};
|
|
98
|
+
function generateServerConfig(opts) {
|
|
99
|
+
const config = {
|
|
100
|
+
server: {
|
|
101
|
+
http: {
|
|
102
|
+
addr: "127.0.0.1:18080"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
auth: {
|
|
106
|
+
type: "none"
|
|
107
|
+
},
|
|
108
|
+
policies: {
|
|
109
|
+
system_dir: "/etc/agentsh/system",
|
|
110
|
+
dir: "/etc/agentsh",
|
|
111
|
+
default: "policy"
|
|
112
|
+
},
|
|
113
|
+
workspace: opts.workspace,
|
|
114
|
+
sandbox: {
|
|
115
|
+
enabled: true,
|
|
116
|
+
allow_degraded: true
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
if (opts.watchtower) config.watchtower = opts.watchtower;
|
|
120
|
+
if (opts.enforceRedirects) config.enforce_redirects = true;
|
|
121
|
+
if (opts.realPaths) config.real_paths = true;
|
|
122
|
+
const feeds = opts.threatFeeds === false ? void 0 : opts.threatFeeds ?? defaultThreatFeeds;
|
|
123
|
+
if (feeds) {
|
|
124
|
+
config.threat_feeds = {
|
|
125
|
+
enabled: true,
|
|
126
|
+
action: feeds.action ?? "deny",
|
|
127
|
+
feeds: feeds.feeds.map((f) => ({
|
|
128
|
+
name: f.name,
|
|
129
|
+
url: f.url,
|
|
130
|
+
format: f.format,
|
|
131
|
+
refresh_interval: f.refreshInterval ?? "6h"
|
|
132
|
+
})),
|
|
133
|
+
...feeds.allowlist?.length ? { allowlist: feeds.allowlist } : {}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return yaml.dump(config, { lineWidth: -1 });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/core/traceparent.ts
|
|
140
|
+
async function getTraceparent() {
|
|
141
|
+
try {
|
|
142
|
+
const { trace } = await import("./esm-7TZRRYDK.js");
|
|
143
|
+
const span = trace.getActiveSpan();
|
|
144
|
+
const ctx = span?.spanContext();
|
|
145
|
+
if (!ctx?.traceId || ctx.traceId === "00000000000000000000000000000000") {
|
|
146
|
+
return void 0;
|
|
147
|
+
}
|
|
148
|
+
const flags = (ctx.traceFlags ?? 0).toString(16).padStart(2, "0");
|
|
149
|
+
return `00-${ctx.traceId}-${ctx.spanId}-${flags}`;
|
|
150
|
+
} catch {
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/core/provision.ts
|
|
156
|
+
var SECURITY_MODE_RANK = {
|
|
157
|
+
full: 4,
|
|
158
|
+
landlock: 3,
|
|
159
|
+
"landlock-only": 2,
|
|
160
|
+
minimal: 1
|
|
161
|
+
};
|
|
162
|
+
function isWeakerThan(detected, required) {
|
|
163
|
+
return SECURITY_MODE_RANK[detected] < SECURITY_MODE_RANK[required];
|
|
164
|
+
}
|
|
165
|
+
function mapArch(uname) {
|
|
166
|
+
const trimmed = uname.trim();
|
|
167
|
+
if (trimmed === "x86_64") return "linux_amd64";
|
|
168
|
+
if (trimmed === "aarch64") return "linux_arm64";
|
|
169
|
+
throw new ProvisioningError({
|
|
170
|
+
phase: "install",
|
|
171
|
+
command: "uname -m",
|
|
172
|
+
stderr: `Unsupported architecture: ${trimmed}`
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
var AGENTSH_PATHS = ["/usr/local/bin/agentsh", "/usr/bin/agentsh"];
|
|
176
|
+
async function binaryExists(adapter) {
|
|
177
|
+
for (const path of AGENTSH_PATHS) {
|
|
178
|
+
const found = adapter.fileExists ? await adapter.fileExists(path) : (await adapter.exec("test", ["-f", path])).exitCode === 0;
|
|
179
|
+
if (found) return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
function sleep(ms) {
|
|
184
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
185
|
+
}
|
|
186
|
+
async function provision(adapter, config = {}) {
|
|
187
|
+
const {
|
|
188
|
+
policy: rawPolicy,
|
|
189
|
+
workspace = "/workspace",
|
|
190
|
+
watchtower,
|
|
191
|
+
installStrategy = "download",
|
|
192
|
+
agentshVersion = PINNED_VERSION,
|
|
193
|
+
agentshArch: archOverride,
|
|
194
|
+
agentshBinaryUrl,
|
|
195
|
+
agentshChecksum,
|
|
196
|
+
skipIntegrityCheck = false,
|
|
197
|
+
minimumSecurityMode,
|
|
198
|
+
realPaths: realPathsOverride,
|
|
199
|
+
enforceRedirects = false,
|
|
200
|
+
traceParent,
|
|
201
|
+
policyName = "policy",
|
|
202
|
+
threatFeeds
|
|
203
|
+
} = config;
|
|
204
|
+
const policy = rawPolicy ? validatePolicy(rawPolicy) : agentDefault();
|
|
205
|
+
let securityMode = "full";
|
|
206
|
+
if (installStrategy === "running") {
|
|
207
|
+
securityMode = await detectSecurityMode(adapter);
|
|
208
|
+
if (minimumSecurityMode && isWeakerThan(securityMode, minimumSecurityMode)) {
|
|
209
|
+
throw new ProvisioningError({
|
|
210
|
+
phase: "install",
|
|
211
|
+
command: "agentsh detect --output json",
|
|
212
|
+
stderr: `Detected security mode '${securityMode}' is weaker than required '${minimumSecurityMode}'`
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
await healthCheck(adapter);
|
|
216
|
+
const envResult = await adapter.exec("sh", ["-c", "echo $AGENTSH_SESSION_ID"]);
|
|
217
|
+
const sessionId2 = envResult.stdout.trim();
|
|
218
|
+
if (!sessionId2) {
|
|
219
|
+
throw new ProvisioningError({
|
|
220
|
+
phase: "session",
|
|
221
|
+
command: "echo $AGENTSH_SESSION_ID",
|
|
222
|
+
stderr: "AGENTSH_SESSION_ID not set \u2014 running strategy requires a pre-created session"
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return { sessionId: sessionId2, securityMode, passthrough: true };
|
|
226
|
+
}
|
|
227
|
+
const exists = await binaryExists(adapter);
|
|
228
|
+
if (installStrategy === "preinstalled") {
|
|
229
|
+
if (!exists) {
|
|
230
|
+
throw new ProvisioningError({
|
|
231
|
+
phase: "install",
|
|
232
|
+
command: AGENTSH_PATHS.map((p) => `test -f ${p}`).join(" || "),
|
|
233
|
+
stderr: "Binary not found but installStrategy is preinstalled"
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
} else if (installStrategy === "download" || installStrategy === "upload") {
|
|
237
|
+
if (!exists) {
|
|
238
|
+
const arch = archOverride ?? await detectArch(adapter);
|
|
239
|
+
if (installStrategy === "download") {
|
|
240
|
+
await downloadBinary(adapter, agentshVersion, arch, agentshBinaryUrl);
|
|
241
|
+
} else {
|
|
242
|
+
await uploadBinary(adapter, agentshVersion, arch, agentshBinaryUrl);
|
|
243
|
+
}
|
|
244
|
+
if (!skipIntegrityCheck) {
|
|
245
|
+
await verifyChecksum(
|
|
246
|
+
adapter,
|
|
247
|
+
agentshVersion,
|
|
248
|
+
arch,
|
|
249
|
+
agentshChecksum,
|
|
250
|
+
"/tmp/agentsh.tar.gz"
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const binaries = [
|
|
254
|
+
{ src: "/tmp/agentsh", dest: "/usr/local/bin/agentsh" },
|
|
255
|
+
{ src: "/tmp/agentsh-shell-shim", dest: "/usr/bin/agentsh-shell-shim" },
|
|
256
|
+
{ src: "/tmp/agentsh-unixwrap", dest: "/usr/local/bin/agentsh-unixwrap" }
|
|
257
|
+
];
|
|
258
|
+
for (const { src, dest } of binaries) {
|
|
259
|
+
const installResult = await adapter.exec(
|
|
260
|
+
"install",
|
|
261
|
+
["-m", "0755", src, dest],
|
|
262
|
+
{ sudo: true }
|
|
263
|
+
);
|
|
264
|
+
if (installResult.exitCode !== 0) {
|
|
265
|
+
throw new ProvisioningError({
|
|
266
|
+
phase: "install",
|
|
267
|
+
command: `install -m 0755 ${src} ${dest}`,
|
|
268
|
+
stderr: installResult.stderr
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
securityMode = await detectSecurityMode(adapter);
|
|
275
|
+
if (minimumSecurityMode && isWeakerThan(securityMode, minimumSecurityMode)) {
|
|
276
|
+
throw new ProvisioningError({
|
|
277
|
+
phase: "install",
|
|
278
|
+
command: "agentsh detect --json",
|
|
279
|
+
stderr: `Detected security mode '${securityMode}' is weaker than required '${minimumSecurityMode}'`
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
const hasFuse = securityMode === "full" || securityMode === "landlock";
|
|
283
|
+
const realPaths = realPathsOverride ?? hasFuse;
|
|
284
|
+
const shimResult = await adapter.exec(
|
|
285
|
+
"agentsh",
|
|
286
|
+
[
|
|
287
|
+
"shim",
|
|
288
|
+
"install-shell",
|
|
289
|
+
"--root",
|
|
290
|
+
"/",
|
|
291
|
+
"--shim",
|
|
292
|
+
"/usr/bin/agentsh-shell-shim",
|
|
293
|
+
"--bash",
|
|
294
|
+
"--i-understand-this-modifies-the-host"
|
|
295
|
+
],
|
|
296
|
+
{ sudo: true }
|
|
297
|
+
);
|
|
298
|
+
if (shimResult.exitCode !== 0) {
|
|
299
|
+
throw new ProvisioningError({
|
|
300
|
+
phase: "install",
|
|
301
|
+
command: "agentsh shim install-shell",
|
|
302
|
+
stderr: shimResult.stderr
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const mkdirResult = await adapter.exec(
|
|
306
|
+
"mkdir",
|
|
307
|
+
["-p", "/etc/agentsh/system"],
|
|
308
|
+
{ sudo: true }
|
|
309
|
+
);
|
|
310
|
+
if (mkdirResult.exitCode !== 0) {
|
|
311
|
+
throw new ProvisioningError({
|
|
312
|
+
phase: "policy",
|
|
313
|
+
command: "mkdir -p /etc/agentsh/system",
|
|
314
|
+
stderr: mkdirResult.stderr
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
await adapter.exec("chmod", ["-R", "777", "/etc/agentsh/"], { sudo: true });
|
|
318
|
+
await adapter.writeFile(
|
|
319
|
+
"/etc/agentsh/system/policy.yml",
|
|
320
|
+
systemPolicyYaml(),
|
|
321
|
+
{ sudo: true }
|
|
322
|
+
);
|
|
323
|
+
await adapter.writeFile(
|
|
324
|
+
"/etc/agentsh/policy.yml",
|
|
325
|
+
serializePolicy(policy),
|
|
326
|
+
{ sudo: true }
|
|
327
|
+
);
|
|
328
|
+
const serverConfig = generateServerConfig({
|
|
329
|
+
workspace,
|
|
330
|
+
watchtower,
|
|
331
|
+
enforceRedirects,
|
|
332
|
+
realPaths,
|
|
333
|
+
threatFeeds
|
|
334
|
+
});
|
|
335
|
+
await adapter.writeFile("/etc/agentsh/config.yml", serverConfig, {
|
|
336
|
+
sudo: true
|
|
337
|
+
});
|
|
338
|
+
const chmodDirResult = await adapter.exec(
|
|
339
|
+
"find",
|
|
340
|
+
["/etc/agentsh", "-type", "d", "-exec", "chmod", "555", "{}", "+"],
|
|
341
|
+
{ sudo: true }
|
|
342
|
+
);
|
|
343
|
+
if (chmodDirResult.exitCode !== 0) {
|
|
344
|
+
throw new ProvisioningError({
|
|
345
|
+
phase: "policy",
|
|
346
|
+
command: "find /etc/agentsh -type d -exec chmod 555 {} +",
|
|
347
|
+
stderr: chmodDirResult.stderr
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
const chmodFileResult = await adapter.exec(
|
|
351
|
+
"find",
|
|
352
|
+
["/etc/agentsh", "-type", "f", "-exec", "chmod", "444", "{}", "+"],
|
|
353
|
+
{ sudo: true }
|
|
354
|
+
);
|
|
355
|
+
if (chmodFileResult.exitCode !== 0) {
|
|
356
|
+
throw new ProvisioningError({
|
|
357
|
+
phase: "policy",
|
|
358
|
+
command: "find /etc/agentsh -type f -exec chmod 444 {} +",
|
|
359
|
+
stderr: chmodFileResult.stderr
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
const chownResult = await adapter.exec(
|
|
363
|
+
"chown",
|
|
364
|
+
["-R", "root:root", "/etc/agentsh/"],
|
|
365
|
+
{ sudo: true }
|
|
366
|
+
);
|
|
367
|
+
if (chownResult.exitCode !== 0) {
|
|
368
|
+
throw new ProvisioningError({
|
|
369
|
+
phase: "policy",
|
|
370
|
+
command: "chown -R root:root /etc/agentsh/",
|
|
371
|
+
stderr: chownResult.stderr
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
await adapter.exec("mkdir", ["-p", workspace], { sudo: true });
|
|
375
|
+
const serverResult = await adapter.exec(
|
|
376
|
+
"agentsh",
|
|
377
|
+
["server", "--config", "/etc/agentsh/config.yml"],
|
|
378
|
+
{ detached: true, sudo: true }
|
|
379
|
+
);
|
|
380
|
+
if (serverResult.exitCode !== 0) {
|
|
381
|
+
throw new ProvisioningError({
|
|
382
|
+
phase: "startup",
|
|
383
|
+
command: "agentsh server --config /etc/agentsh/config.yml",
|
|
384
|
+
stderr: serverResult.stderr
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
await healthCheck(adapter);
|
|
388
|
+
const sessionResult = await adapter.exec("agentsh", [
|
|
389
|
+
"session",
|
|
390
|
+
"create",
|
|
391
|
+
"--workspace",
|
|
392
|
+
workspace,
|
|
393
|
+
"--policy",
|
|
394
|
+
"policy"
|
|
395
|
+
]);
|
|
396
|
+
if (sessionResult.exitCode !== 0) {
|
|
397
|
+
throw new ProvisioningError({
|
|
398
|
+
phase: "session",
|
|
399
|
+
command: "agentsh session create",
|
|
400
|
+
stderr: sessionResult.stderr
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
let sessionId;
|
|
404
|
+
try {
|
|
405
|
+
const sessionData = JSON.parse(sessionResult.stdout);
|
|
406
|
+
sessionId = sessionData.session_id;
|
|
407
|
+
} catch {
|
|
408
|
+
const match = sessionResult.stdout.match(/Session\s+(session-[^\s]+)/);
|
|
409
|
+
if (match) {
|
|
410
|
+
sessionId = match[1];
|
|
411
|
+
} else {
|
|
412
|
+
throw new ProvisioningError({
|
|
413
|
+
phase: "session",
|
|
414
|
+
command: "agentsh session create",
|
|
415
|
+
stderr: `Failed to parse session output: ${sessionResult.stdout}`
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const effectiveTraceParent = traceParent ?? await getTraceparent();
|
|
420
|
+
if (effectiveTraceParent) {
|
|
421
|
+
await adapter.exec("curl", [
|
|
422
|
+
"-X",
|
|
423
|
+
"PUT",
|
|
424
|
+
`http://127.0.0.1:18080/sessions/${sessionId}/trace-context`,
|
|
425
|
+
"-H",
|
|
426
|
+
"Content-Type: application/json",
|
|
427
|
+
"-d",
|
|
428
|
+
JSON.stringify({ traceparent: effectiveTraceParent })
|
|
429
|
+
]);
|
|
430
|
+
}
|
|
431
|
+
return { sessionId, securityMode };
|
|
432
|
+
}
|
|
433
|
+
async function detectArch(adapter) {
|
|
434
|
+
const result = await adapter.exec("uname", ["-m"]);
|
|
435
|
+
if (result.exitCode !== 0) {
|
|
436
|
+
throw new ProvisioningError({
|
|
437
|
+
phase: "install",
|
|
438
|
+
command: "uname -m",
|
|
439
|
+
stderr: result.stderr
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
return mapArch(result.stdout);
|
|
443
|
+
}
|
|
444
|
+
async function downloadBinary(adapter, version, arch, overrideUrl) {
|
|
445
|
+
const url = binaryUrl(version, arch, overrideUrl);
|
|
446
|
+
const curlResult = await adapter.exec("curl", [
|
|
447
|
+
"-fsSL",
|
|
448
|
+
url,
|
|
449
|
+
"-o",
|
|
450
|
+
"/tmp/agentsh.tar.gz"
|
|
451
|
+
]);
|
|
452
|
+
if (curlResult.exitCode !== 0) {
|
|
453
|
+
let wgetResult;
|
|
454
|
+
try {
|
|
455
|
+
wgetResult = await adapter.exec("wget", [
|
|
456
|
+
"-q",
|
|
457
|
+
url,
|
|
458
|
+
"-O",
|
|
459
|
+
"/tmp/agentsh.tar.gz"
|
|
460
|
+
]);
|
|
461
|
+
} catch {
|
|
462
|
+
throw new ProvisioningError({
|
|
463
|
+
phase: "install",
|
|
464
|
+
command: `curl -fsSL ${url} -o /tmp/agentsh.tar.gz`,
|
|
465
|
+
stderr: curlResult.stderr || "Download failed (curl failed, wget not available)"
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
if (wgetResult.exitCode !== 0) {
|
|
469
|
+
throw new ProvisioningError({
|
|
470
|
+
phase: "install",
|
|
471
|
+
command: `wget -q ${url} -O /tmp/agentsh.tar.gz`,
|
|
472
|
+
stderr: wgetResult.stderr
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const tarResult = await adapter.exec("tar", [
|
|
477
|
+
"xz",
|
|
478
|
+
"-C",
|
|
479
|
+
"/tmp/",
|
|
480
|
+
"-f",
|
|
481
|
+
"/tmp/agentsh.tar.gz"
|
|
482
|
+
]);
|
|
483
|
+
if (tarResult.exitCode !== 0) {
|
|
484
|
+
throw new ProvisioningError({
|
|
485
|
+
phase: "install",
|
|
486
|
+
command: "tar xz -C /tmp/ -f /tmp/agentsh.tar.gz",
|
|
487
|
+
stderr: tarResult.stderr
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async function uploadBinary(adapter, version, arch, overrideUrl) {
|
|
492
|
+
const url = binaryUrl(version, arch, overrideUrl);
|
|
493
|
+
const response = await fetch(url);
|
|
494
|
+
if (!response.ok) {
|
|
495
|
+
throw new ProvisioningError({
|
|
496
|
+
phase: "install",
|
|
497
|
+
command: `fetch ${url}`,
|
|
498
|
+
stderr: `HTTP ${response.status}: ${response.statusText}`
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
502
|
+
await adapter.writeFile("/tmp/agentsh.tar.gz", buffer);
|
|
503
|
+
const tarResult = await adapter.exec("tar", [
|
|
504
|
+
"xz",
|
|
505
|
+
"-C",
|
|
506
|
+
"/tmp/",
|
|
507
|
+
"-f",
|
|
508
|
+
"/tmp/agentsh.tar.gz"
|
|
509
|
+
]);
|
|
510
|
+
if (tarResult.exitCode !== 0) {
|
|
511
|
+
throw new ProvisioningError({
|
|
512
|
+
phase: "install",
|
|
513
|
+
command: "tar xz -C /tmp/ -f /tmp/agentsh.tar.gz",
|
|
514
|
+
stderr: tarResult.stderr
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
async function verifyChecksum(adapter, version, arch, checksumOverride, filePath) {
|
|
519
|
+
const expected = getChecksum(version, arch, checksumOverride);
|
|
520
|
+
const commands = buildVerifyCommand(filePath);
|
|
521
|
+
let actual;
|
|
522
|
+
for (const cmd of commands) {
|
|
523
|
+
const result = await adapter.exec("sh", ["-c", cmd]);
|
|
524
|
+
if (result.exitCode === 0 && result.stdout.trim()) {
|
|
525
|
+
actual = result.stdout.trim();
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (actual === void 0) {
|
|
530
|
+
throw new ProvisioningError({
|
|
531
|
+
phase: "install",
|
|
532
|
+
command: "sha256sum / shasum / openssl",
|
|
533
|
+
stderr: "No checksum tool available in sandbox"
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if (actual !== expected) {
|
|
537
|
+
throw new IntegrityError({
|
|
538
|
+
expected,
|
|
539
|
+
actual,
|
|
540
|
+
message: `Checksum mismatch: expected ${expected}, got ${actual}`
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async function detectSecurityMode(adapter) {
|
|
545
|
+
const result = await adapter.exec("agentsh", ["detect", "--output", "json"]);
|
|
546
|
+
if (result.exitCode !== 0) {
|
|
547
|
+
throw new ProvisioningError({
|
|
548
|
+
phase: "install",
|
|
549
|
+
command: "agentsh detect --output json",
|
|
550
|
+
stderr: result.stderr
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
const jsonOutput = result.stderr || result.stdout;
|
|
554
|
+
let parsed;
|
|
555
|
+
try {
|
|
556
|
+
parsed = JSON.parse(jsonOutput);
|
|
557
|
+
} catch {
|
|
558
|
+
throw new ProvisioningError({
|
|
559
|
+
phase: "install",
|
|
560
|
+
command: "agentsh detect --output json",
|
|
561
|
+
stderr: `Failed to parse detect JSON: ${jsonOutput.slice(0, 200)}`
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
const mode = parsed.security_mode;
|
|
565
|
+
const validModes = ["full", "landlock", "landlock-only", "minimal"];
|
|
566
|
+
if (!validModes.includes(mode)) {
|
|
567
|
+
throw new ProvisioningError({
|
|
568
|
+
phase: "install",
|
|
569
|
+
command: "agentsh detect --output json",
|
|
570
|
+
stderr: `Unknown security mode: '${mode}'`
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
return mode;
|
|
574
|
+
}
|
|
575
|
+
async function healthCheck(adapter) {
|
|
576
|
+
const maxRetries = 10;
|
|
577
|
+
const delayMs = 500;
|
|
578
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
579
|
+
const result = await adapter.exec("curl", [
|
|
580
|
+
"-sf",
|
|
581
|
+
"http://127.0.0.1:18080/health"
|
|
582
|
+
]);
|
|
583
|
+
if (result.exitCode === 0) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (i < maxRetries - 1) {
|
|
587
|
+
await sleep(delayMs);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
throw new ProvisioningError({
|
|
591
|
+
phase: "startup",
|
|
592
|
+
command: "curl http://127.0.0.1:18080/health",
|
|
593
|
+
stderr: "Health check failed after 10 attempts"
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/core/runtime.ts
|
|
598
|
+
async function traceEnv() {
|
|
599
|
+
const tp = await getTraceparent();
|
|
600
|
+
return tp ? { TRACEPARENT: tp } : void 0;
|
|
601
|
+
}
|
|
602
|
+
function parseExecJson(raw) {
|
|
603
|
+
try {
|
|
604
|
+
const json = JSON.parse(raw.stdout);
|
|
605
|
+
const result = json.result ?? {};
|
|
606
|
+
return {
|
|
607
|
+
exitCode: result.exit_code ?? raw.exitCode,
|
|
608
|
+
stdout: result.stdout ?? "",
|
|
609
|
+
stderr: result.stderr ?? result.error?.message ?? ""
|
|
610
|
+
};
|
|
611
|
+
} catch {
|
|
612
|
+
return raw;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function createSecuredSandbox(adapter, sessionId, securityMode, options) {
|
|
616
|
+
if (options?.passthrough) {
|
|
617
|
+
return createPassthroughSandbox(adapter, sessionId, securityMode);
|
|
618
|
+
}
|
|
619
|
+
return createAgentshSandbox(adapter, sessionId, securityMode);
|
|
620
|
+
}
|
|
621
|
+
function createPassthroughSandbox(adapter, sessionId, securityMode) {
|
|
622
|
+
return {
|
|
623
|
+
sessionId,
|
|
624
|
+
securityMode,
|
|
625
|
+
async exec(command, opts) {
|
|
626
|
+
const result = await adapter.exec("bash", ["-c", command], {
|
|
627
|
+
cwd: opts?.cwd
|
|
628
|
+
});
|
|
629
|
+
return result;
|
|
630
|
+
},
|
|
631
|
+
async writeFile(path, content) {
|
|
632
|
+
const b64 = Buffer.from(content, "utf-8").toString("base64");
|
|
633
|
+
const result = await adapter.exec("sh", [
|
|
634
|
+
"-c",
|
|
635
|
+
'printf "%s" "$1" | base64 -d > "$2"',
|
|
636
|
+
"_",
|
|
637
|
+
b64,
|
|
638
|
+
path
|
|
639
|
+
]);
|
|
640
|
+
if (result.exitCode !== 0) {
|
|
641
|
+
return {
|
|
642
|
+
success: false,
|
|
643
|
+
path,
|
|
644
|
+
error: result.stderr || "writeFile failed"
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
return { success: true, path };
|
|
648
|
+
},
|
|
649
|
+
async readFile(path) {
|
|
650
|
+
const result = await adapter.exec("cat", [path]);
|
|
651
|
+
if (result.exitCode !== 0) {
|
|
652
|
+
return {
|
|
653
|
+
success: false,
|
|
654
|
+
path,
|
|
655
|
+
error: result.stderr || "readFile failed"
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
return { success: true, path, content: result.stdout };
|
|
659
|
+
},
|
|
660
|
+
async stop() {
|
|
661
|
+
await adapter.stop?.();
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function createAgentshSandbox(adapter, sessionId, securityMode) {
|
|
666
|
+
return {
|
|
667
|
+
sessionId,
|
|
668
|
+
securityMode,
|
|
669
|
+
async exec(command, opts) {
|
|
670
|
+
const args = [
|
|
671
|
+
"exec",
|
|
672
|
+
"--output",
|
|
673
|
+
"json",
|
|
674
|
+
sessionId,
|
|
675
|
+
"--",
|
|
676
|
+
"bash",
|
|
677
|
+
"-c",
|
|
678
|
+
command
|
|
679
|
+
];
|
|
680
|
+
const env = await traceEnv();
|
|
681
|
+
const execOpts = { cwd: opts?.cwd, env };
|
|
682
|
+
const result = await adapter.exec("agentsh", args, execOpts);
|
|
683
|
+
if (isTransportFailure(result)) {
|
|
684
|
+
throw new RuntimeError({
|
|
685
|
+
sessionId,
|
|
686
|
+
command,
|
|
687
|
+
stderr: result.stderr
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
return parseExecJson(result);
|
|
691
|
+
},
|
|
692
|
+
async writeFile(path, content) {
|
|
693
|
+
const b64 = Buffer.from(content, "utf-8").toString("base64");
|
|
694
|
+
const args = [
|
|
695
|
+
"exec",
|
|
696
|
+
sessionId,
|
|
697
|
+
"--",
|
|
698
|
+
"sh",
|
|
699
|
+
"-c",
|
|
700
|
+
'printf "%s" "$1" | base64 -d > "$2"',
|
|
701
|
+
"_",
|
|
702
|
+
b64,
|
|
703
|
+
path
|
|
704
|
+
];
|
|
705
|
+
const env = await traceEnv();
|
|
706
|
+
const result = await adapter.exec("agentsh", args, { env });
|
|
707
|
+
if (isTransportFailure(result)) {
|
|
708
|
+
throw new RuntimeError({
|
|
709
|
+
sessionId,
|
|
710
|
+
command: `writeFile ${path}`,
|
|
711
|
+
stderr: result.stderr
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
if (result.exitCode !== 0) {
|
|
715
|
+
return {
|
|
716
|
+
success: false,
|
|
717
|
+
path,
|
|
718
|
+
error: result.stderr || "writeFile failed"
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
return { success: true, path };
|
|
722
|
+
},
|
|
723
|
+
async readFile(path) {
|
|
724
|
+
const args = ["exec", sessionId, "--", "cat", path];
|
|
725
|
+
const env = await traceEnv();
|
|
726
|
+
const result = await adapter.exec("agentsh", args, { env });
|
|
727
|
+
if (isTransportFailure(result)) {
|
|
728
|
+
throw new RuntimeError({
|
|
729
|
+
sessionId,
|
|
730
|
+
command: `readFile ${path}`,
|
|
731
|
+
stderr: result.stderr
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
if (result.exitCode !== 0) {
|
|
735
|
+
return {
|
|
736
|
+
success: false,
|
|
737
|
+
path,
|
|
738
|
+
error: result.stderr || "readFile failed"
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
return { success: true, path, content: result.stdout };
|
|
742
|
+
},
|
|
743
|
+
async stop() {
|
|
744
|
+
await adapter.stop?.();
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function isTransportFailure(result) {
|
|
749
|
+
return result.exitCode === 127 && result.stderr.includes("agentsh");
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/api.ts
|
|
753
|
+
async function secureSandbox(adapter, config) {
|
|
754
|
+
const resolvedConfig = config ?? {};
|
|
755
|
+
const { sessionId, securityMode, passthrough } = await provision(adapter, {
|
|
756
|
+
workspace: "/workspace",
|
|
757
|
+
...resolvedConfig
|
|
758
|
+
});
|
|
759
|
+
return createSecuredSandbox(adapter, sessionId, securityMode, { passthrough });
|
|
760
|
+
}
|
|
761
|
+
export {
|
|
762
|
+
AgentSHError,
|
|
763
|
+
IncompatibleProviderVersionError,
|
|
764
|
+
IntegrityError,
|
|
765
|
+
MissingPeerDependencyError,
|
|
766
|
+
PolicyValidationError,
|
|
767
|
+
ProvisioningError,
|
|
768
|
+
RuntimeError,
|
|
769
|
+
adapters_exports as adapters,
|
|
770
|
+
defaultThreatFeeds,
|
|
771
|
+
policies_exports as policies,
|
|
772
|
+
secureSandbox
|
|
773
|
+
};
|
|
774
|
+
//# sourceMappingURL=index.js.map
|