@agentsh/secure-sandbox 0.1.6 → 0.1.7

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.
@@ -0,0 +1,60 @@
1
+ import { vercel } from './adapters/vercel.js';
2
+ import { e2b } from './adapters/e2b.js';
3
+ import { daytona } from './adapters/daytona.js';
4
+ import { cloudflare } from './adapters/cloudflare.js';
5
+ import { blaxel } from './adapters/blaxel.js';
6
+ import { S as SandboxAdapter, a as SecureConfig } from './types-DFMGk2GV.js';
7
+
8
+ declare function sprites(sprite: any): SandboxAdapter;
9
+ /**
10
+ * Returns Sprites-optimized defaults for SecureConfig.
11
+ * Spread into your secureSandbox() call:
12
+ *
13
+ * secureSandbox(sprites(s), { ...spritesDefaults(), ...yourOverrides })
14
+ */
15
+ declare function spritesDefaults(): Partial<SecureConfig>;
16
+
17
+ /**
18
+ * Wraps a Modal Sandbox object into a SandboxAdapter.
19
+ *
20
+ * Modal sandboxes run on gVisor which lacks seccomp user-notify and
21
+ * full FUSE support. Use `modalDefaults()` to get ptrace-optimized
22
+ * configuration that works on gVisor.
23
+ *
24
+ * The `sandbox` parameter is typed as `any` to avoid a hard dependency
25
+ * on the Modal SDK — pass any object whose `.exec()` accepts variadic
26
+ * string arguments and returns a process with `.stdout.read()`,
27
+ * `.stderr.read()`, `.returncode`, and `.wait()`.
28
+ */
29
+ declare function modal(sandbox: any): SandboxAdapter;
30
+ /**
31
+ * Returns Modal-optimized defaults for SecureConfig.
32
+ *
33
+ * Key differences from other providers:
34
+ * - ptrace enabled (gVisor blocks seccomp user-notify)
35
+ * - seccomp_prefilter disabled (gVisor blocks BPF injection)
36
+ * - FUSE deferred (may not be available on gVisor)
37
+ * - unix_sockets disabled (mutually exclusive with ptrace)
38
+ * - cgroups disabled (Modal handles resource limits)
39
+ * - allow_degraded enabled (graceful fallback for FUSE/seccomp)
40
+ *
41
+ * Spread into your secureSandbox() call:
42
+ *
43
+ * secureSandbox(modal(sb), { ...modalDefaults(), ...yourOverrides })
44
+ */
45
+ declare function modalDefaults(): Partial<SecureConfig>;
46
+
47
+ declare const index_blaxel: typeof blaxel;
48
+ declare const index_cloudflare: typeof cloudflare;
49
+ declare const index_daytona: typeof daytona;
50
+ declare const index_e2b: typeof e2b;
51
+ declare const index_modal: typeof modal;
52
+ declare const index_modalDefaults: typeof modalDefaults;
53
+ declare const index_sprites: typeof sprites;
54
+ declare const index_spritesDefaults: typeof spritesDefaults;
55
+ declare const index_vercel: typeof vercel;
56
+ declare namespace index {
57
+ export { index_blaxel as blaxel, index_cloudflare as cloudflare, index_daytona as daytona, index_e2b as e2b, index_modal as modal, index_modalDefaults as modalDefaults, index_sprites as sprites, index_spritesDefaults as spritesDefaults, index_vercel as vercel };
58
+ }
59
+
60
+ export { modalDefaults as a, spritesDefaults as b, index as i, modal as m, sprites as s };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { S as SandboxAdapter, a as SecureConfig, b as SecuredSandbox } from './types-S_fIEFHD.js';
2
- export { E as ExecResult, I as InstallStrategy, L as LicenseSpdxMatch, P as PackageChecksConfig, c as PackageMatch, d as PackageRule, e as ProviderConfig, R as ReadFileResult, f as SecurityMode, T as ThreatFeed, g as ThreatFeedsConfig, W as WriteFileResult, h as defaultThreatFeeds } from './types-S_fIEFHD.js';
3
- export { P as PolicyDefinition, i as policies } from './index-Nmlhw9oj.js';
1
+ import { S as SandboxAdapter, a as SecureConfig, b as SecuredSandbox } from './types-DFMGk2GV.js';
2
+ export { E as ExecResult, I as InstallStrategy, L as LicenseSpdxMatch, P as PackageChecksConfig, c as PackageMatch, d as PackageRule, e as ProviderConfig, R as ReadFileResult, f as SecurityMode, T as ThreatFeed, g as ThreatFeedsConfig, W as WriteFileResult, h as defaultThreatFeeds } from './types-DFMGk2GV.js';
3
+ export { P as PolicyDefinition, i as policies } from './index-CedRtlB6.js';
4
4
  import { ZodIssue } from 'zod';
5
- export { i as adapters } from './index-D6DG8Lpi.js';
5
+ export { i as adapters } from './index-TyzWAIUD.js';
6
6
  import './adapters/vercel.js';
7
7
  import './adapters/e2b.js';
8
8
  import './adapters/daytona.js';
package/dist/index.js CHANGED
@@ -1,12 +1,17 @@
1
1
  import {
2
2
  adapters_exports
3
- } from "./chunk-KXCR2ZML.js";
3
+ } from "./chunk-5IG6ABIZ.js";
4
4
  import "./chunk-UYEAO27E.js";
5
5
  import "./chunk-LMN3KM53.js";
6
6
  import "./chunk-45FKFVMC.js";
7
7
  import "./chunk-2P37YGN7.js";
8
8
  import "./chunk-OANLKSOD.js";
9
9
  import "./chunk-JY5ERJTX.js";
10
+ import {
11
+ policies_exports,
12
+ serializePolicy,
13
+ systemPolicyYaml
14
+ } from "./chunk-4FJHYLAB.js";
10
15
  import {
11
16
  AgentSHError,
12
17
  IncompatibleProviderVersionError,
@@ -16,23 +21,20 @@ import {
16
21
  ProvisioningError,
17
22
  RuntimeError,
18
23
  agentDefault,
19
- policies_exports,
20
- serializePolicy,
21
- systemPolicyYaml,
22
24
  validatePolicy
23
- } from "./chunk-GFPHTJLU.js";
25
+ } from "./chunk-LNDICGZU.js";
24
26
  import "./chunk-PZ5AY32C.js";
25
27
 
26
28
  // src/core/integrity.ts
27
- var PINNED_VERSION = "0.15.0";
29
+ var PINNED_VERSION = "0.16.2";
28
30
  var CHECKSUMS = {
31
+ "0.16.2": {
32
+ linux_amd64: "7ff357066a61694626d4c19afa92fdf368318bced9be90391cc2f3808976f995",
33
+ linux_arm64: "a48b3e4a60804cca98326619a68409e8ee83556d69ee2cf5d574e4361e0c19c6"
34
+ },
29
35
  "0.15.0": {
30
36
  linux_amd64: "89f7ebbfd75ffd961245ec62b2602fd0cc387740502ac858dbc39c367c5699c5",
31
37
  linux_arm64: "3fabbd749f9e98fb9f96ddfc94c389a6868cda7ed3668daa8440c39ceec85f3b"
32
- },
33
- "0.14.0": {
34
- linux_amd64: "2ab8ba0d6637fe1a5badf840c3db197161a6f9865d721ed216029d229b1b9bbc",
35
- linux_arm64: "929d18dd9fe36e9b2fa830d7ae64b4fb481853e743ade8674fcfcdc73470ed53"
36
38
  }
37
39
  };
38
40
  function getChecksum(version, arch, override) {
@@ -128,10 +130,20 @@ function generateServerConfig(opts) {
128
130
  },
129
131
  sandbox: {
130
132
  enabled: true,
131
- allow_degraded: true,
132
- fuse: { enabled: true },
133
+ allow_degraded: opts.allowDegraded ?? true,
134
+ // FUSE disabled by default: when agentsh server runs as root and exec
135
+ // users are non-root (e.g. E2B, Daytona), the FUSE workspace-mnt is
136
+ // inaccessible to non-root users causing exec to fail with exit code 2.
137
+ // File policy is still enforced via landlock. Enable FUSE
138
+ // explicitly via serverConfig: { fuse: { enabled: true } } if needed.
139
+ fuse: { enabled: false },
133
140
  network: { enabled: true },
134
- seccomp: { enabled: true }
141
+ // Seccomp NOTIFY disabled by default: many container environments
142
+ // (Daytona, E2B custom images) restrict the seccomp() syscall via their
143
+ // container seccomp profile, causing "install seccomp filter: operation
144
+ // canceled" on every exec. Policy is still enforced via landlock and
145
+ // network rules. When ptrace is enabled, seccomp is also incompatible.
146
+ seccomp: { enabled: false }
135
147
  }
136
148
  };
137
149
  if (opts.watchtower) config.watchtower = opts.watchtower;
@@ -145,7 +157,12 @@ function generateServerConfig(opts) {
145
157
  if (opts.serverTimeouts.maxRequestSize) http.max_request_size = opts.serverTimeouts.maxRequestSize;
146
158
  }
147
159
  if (opts.logging) config.logging = { ...opts.logging };
148
- const sessionsObj = {};
160
+ const sessionsObj = {
161
+ // Default sessions to a writable location outside /etc/agentsh (which is
162
+ // locked to 555/444 during provisioning). v0.16.2+ resolves workspace mount
163
+ // symlinks inside the sessions dir, which requires write access.
164
+ base_dir: "/var/lib/agentsh/sessions"
165
+ };
149
166
  if (opts.realPaths) sessionsObj.real_paths = true;
150
167
  if (opts.sessions) {
151
168
  if (opts.sessions.baseDir) sessionsObj.base_dir = opts.sessions.baseDir;
@@ -168,8 +185,11 @@ function generateServerConfig(opts) {
168
185
  ...opts.sandboxLimits.maxProcesses !== void 0 && { max_processes: opts.sandboxLimits.maxProcesses }
169
186
  };
170
187
  }
171
- if (opts.fuse?.deferred !== void 0) {
172
- config.sandbox.fuse.deferred = opts.fuse.deferred;
188
+ if (opts.fuse) {
189
+ const fuseObj = config.sandbox.fuse;
190
+ if (opts.fuse.deferred !== void 0) fuseObj.deferred = opts.fuse.deferred;
191
+ if (opts.fuse.deferredMarkerFile) fuseObj.deferred_marker_file = opts.fuse.deferredMarkerFile;
192
+ if (opts.fuse.deferredEnableCommand) fuseObj.deferred_enable_command = opts.fuse.deferredEnableCommand;
173
193
  }
174
194
  if (opts.networkIntercept) {
175
195
  const net = config.sandbox.network;
@@ -178,6 +198,7 @@ function generateServerConfig(opts) {
178
198
  }
179
199
  if (opts.seccompDetails) {
180
200
  const sec = config.sandbox.seccomp;
201
+ if (!opts.ptrace?.enabled) sec.enabled = true;
181
202
  if (opts.seccompDetails.execve !== void 0) sec.execve = opts.seccompDetails.execve;
182
203
  if (opts.seccompDetails.fileMonitor) {
183
204
  sec.file_monitor = {
@@ -192,6 +213,32 @@ function generateServerConfig(opts) {
192
213
  if (opts.unixSockets) {
193
214
  config.sandbox.unix_sockets = { ...opts.unixSockets };
194
215
  }
216
+ if (opts.ptrace) {
217
+ const ptraceObj = {};
218
+ if (opts.ptrace.enabled !== void 0) ptraceObj.enabled = opts.ptrace.enabled;
219
+ if (opts.ptrace.attachMode) ptraceObj.attach_mode = opts.ptrace.attachMode;
220
+ if (opts.ptrace.maskTracerPid) ptraceObj.mask_tracer_pid = opts.ptrace.maskTracerPid;
221
+ if (opts.ptrace.trace) {
222
+ const traceObj = {};
223
+ if (opts.ptrace.trace.execve !== void 0) traceObj.execve = opts.ptrace.trace.execve;
224
+ if (opts.ptrace.trace.file !== void 0) traceObj.file = opts.ptrace.trace.file;
225
+ if (opts.ptrace.trace.network !== void 0) traceObj.network = opts.ptrace.trace.network;
226
+ if (opts.ptrace.trace.signal !== void 0) traceObj.signal = opts.ptrace.trace.signal;
227
+ ptraceObj.trace = traceObj;
228
+ }
229
+ if (opts.ptrace.performance) {
230
+ const perfObj = {};
231
+ if (opts.ptrace.performance.seccompPrefilter !== void 0) perfObj.seccomp_prefilter = opts.ptrace.performance.seccompPrefilter;
232
+ if (opts.ptrace.performance.maxTracees !== void 0) perfObj.max_tracees = opts.ptrace.performance.maxTracees;
233
+ if (opts.ptrace.performance.maxHoldMs !== void 0) perfObj.max_hold_ms = opts.ptrace.performance.maxHoldMs;
234
+ ptraceObj.performance = perfObj;
235
+ }
236
+ if (opts.ptrace.onAttachFailure) ptraceObj.on_attach_failure = opts.ptrace.onAttachFailure;
237
+ config.sandbox.ptrace = ptraceObj;
238
+ }
239
+ if (opts.envInject) {
240
+ config.sandbox.env_inject = { ...opts.envInject };
241
+ }
195
242
  if (opts.proxy) {
196
243
  config.proxy = { ...opts.proxy };
197
244
  }
@@ -287,7 +334,8 @@ async function getTraceparent() {
287
334
 
288
335
  // src/core/provision.ts
289
336
  var SECURITY_MODE_RANK = {
290
- full: 4,
337
+ full: 5,
338
+ ptrace: 4,
291
339
  landlock: 3,
292
340
  "landlock-only": 2,
293
341
  minimal: 1
@@ -333,13 +381,22 @@ async function provision(adapter, config = {}) {
333
381
  policyName = "policy",
334
382
  threatFeeds,
335
383
  packageChecks,
384
+ skipShim = false,
336
385
  serverConfig: extendedConfig
337
386
  } = config;
338
387
  const policy = rawPolicy ? validatePolicy(rawPolicy) : agentDefault();
339
388
  let securityMode = "full";
340
389
  if (installStrategy === "running") {
341
390
  await healthCheck(adapter);
342
- securityMode = config.securityMode ?? "full";
391
+ if (config.securityMode) {
392
+ securityMode = config.securityMode;
393
+ } else if (minimumSecurityMode) {
394
+ throw new ProvisioningError({
395
+ phase: "install",
396
+ command: "securityMode check",
397
+ stderr: `Cannot verify security mode in 'running' strategy \u2014 set securityMode explicitly when using minimumSecurityMode`
398
+ });
399
+ }
343
400
  if (minimumSecurityMode && isWeakerThan(securityMode, minimumSecurityMode)) {
344
401
  throw new ProvisioningError({
345
402
  phase: "install",
@@ -371,7 +428,15 @@ async function provision(adapter, config = {}) {
371
428
  });
372
429
  }
373
430
  } else if (installStrategy === "download" || installStrategy === "upload") {
374
- if (!exists) {
431
+ let needsInstall = !exists;
432
+ if (exists && agentshVersion !== "skip-version-check") {
433
+ const versionResult = await adapter.exec("agentsh", ["--version"]);
434
+ const installedVersion = versionResult.stdout.trim().replace(/^v/, "");
435
+ if (!installedVersion.startsWith(agentshVersion)) {
436
+ needsInstall = true;
437
+ }
438
+ }
439
+ if (needsInstall) {
375
440
  const arch = archOverride ?? await detectArch(adapter);
376
441
  if (installStrategy === "download") {
377
442
  await downloadBinary(adapter, agentshVersion, arch, agentshBinaryUrl);
@@ -416,28 +481,29 @@ async function provision(adapter, config = {}) {
416
481
  stderr: `Detected security mode '${securityMode}' is weaker than required '${minimumSecurityMode}'`
417
482
  });
418
483
  }
419
- const hasFuse = securityMode === "full" || securityMode === "landlock";
420
- const realPaths = realPathsOverride ?? hasFuse;
421
- const shimResult = await adapter.exec(
422
- "agentsh",
423
- [
424
- "shim",
425
- "install-shell",
426
- "--root",
427
- "/",
428
- "--shim",
429
- "/usr/bin/agentsh-shell-shim",
430
- "--bash",
431
- "--i-understand-this-modifies-the-host"
432
- ],
433
- { sudo: true }
434
- );
435
- if (shimResult.exitCode !== 0) {
436
- throw new ProvisioningError({
437
- phase: "install",
438
- command: "agentsh shim install-shell",
439
- stderr: shimResult.stderr
440
- });
484
+ const realPaths = realPathsOverride ?? false;
485
+ if (!skipShim) {
486
+ const shimResult = await adapter.exec(
487
+ "agentsh",
488
+ [
489
+ "shim",
490
+ "install-shell",
491
+ "--root",
492
+ "/",
493
+ "--shim",
494
+ "/usr/bin/agentsh-shell-shim",
495
+ "--bash",
496
+ "--i-understand-this-modifies-the-host"
497
+ ],
498
+ { sudo: true }
499
+ );
500
+ if (shimResult.exitCode !== 0) {
501
+ throw new ProvisioningError({
502
+ phase: "install",
503
+ command: "agentsh shim install-shell",
504
+ stderr: shimResult.stderr
505
+ });
506
+ }
441
507
  }
442
508
  const mkdirResult = await adapter.exec(
443
509
  "mkdir",
@@ -508,7 +574,12 @@ async function provision(adapter, config = {}) {
508
574
  stderr: chownResult.stderr
509
575
  });
510
576
  }
511
- await adapter.exec("mkdir", ["-p", workspace], { sudo: true });
577
+ await adapter.exec("mkdir", ["-p", workspace, "/var/lib/agentsh/sessions"], { sudo: true });
578
+ await adapter.exec("chmod", ["755", "/var/lib/agentsh", "/var/lib/agentsh/sessions"], { sudo: true });
579
+ await adapter.exec("sh", [
580
+ "-c",
581
+ 'grep -q user_allow_other /etc/fuse.conf 2>/dev/null || echo "user_allow_other" >> /etc/fuse.conf'
582
+ ], { sudo: true });
512
583
  const serverResult = await adapter.exec(
513
584
  "agentsh",
514
585
  ["server", "--config", "/etc/agentsh/config.yml"],
@@ -553,6 +624,7 @@ async function provision(adapter, config = {}) {
553
624
  });
554
625
  }
555
626
  }
627
+ await adapter.exec("chmod", ["-R", "755", "/var/lib/agentsh/sessions/"], { sudo: true });
556
628
  const effectiveTraceParent = traceParent ?? await getTraceparent();
557
629
  if (effectiveTraceParent) {
558
630
  await adapter.exec("curl", [
@@ -699,7 +771,7 @@ async function detectSecurityMode(adapter) {
699
771
  });
700
772
  }
701
773
  const mode = parsed.security_mode;
702
- const validModes = ["full", "landlock", "landlock-only", "minimal"];
774
+ const validModes = ["full", "ptrace", "landlock", "landlock-only", "minimal"];
703
775
  if (!validModes.includes(mode)) {
704
776
  throw new ProvisioningError({
705
777
  phase: "install",