@agentsh/secure-sandbox 0.1.5 → 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, T as ThreatFeedsConfig } from './types-CUqsllMs.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, g as ThreatFeed, W as WriteFileResult } from './types-CUqsllMs.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-aQ1TVPtG.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';
@@ -11,12 +11,6 @@ import './adapters/blaxel.js';
11
11
 
12
12
  declare function secureSandbox(adapter: SandboxAdapter, config?: SecureConfig): Promise<SecuredSandbox>;
13
13
 
14
- /**
15
- * Default threat feeds: URLhaus (malware) + Phishing.Database (phishing).
16
- * Both are free, open source, and updated frequently.
17
- */
18
- declare const defaultThreatFeeds: ThreatFeedsConfig;
19
-
20
14
  declare class AgentSHError extends Error {
21
15
  constructor(message: string);
22
16
  }
@@ -74,4 +68,4 @@ declare class RuntimeError extends AgentSHError {
74
68
  });
75
69
  }
76
70
 
77
- export { AgentSHError, IncompatibleProviderVersionError, IntegrityError, MissingPeerDependencyError, PolicyValidationError, ProvisioningError, RuntimeError, SandboxAdapter, SecureConfig, SecuredSandbox, ThreatFeedsConfig, defaultThreatFeeds, secureSandbox };
71
+ export { AgentSHError, IncompatibleProviderVersionError, IntegrityError, MissingPeerDependencyError, PolicyValidationError, ProvisioningError, RuntimeError, SandboxAdapter, SecureConfig, SecuredSandbox, secureSandbox };
package/dist/index.js CHANGED
@@ -1,12 +1,17 @@
1
1
  import {
2
2
  adapters_exports
3
- } from "./chunk-L4KFLVNU.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,14 +130,151 @@ 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;
138
- if (opts.realPaths) config.sessions = { real_paths: true };
150
+ if (opts.grpc) {
151
+ config.server.grpc = { enabled: true, addr: opts.grpc.addr };
152
+ }
153
+ if (opts.serverTimeouts) {
154
+ const http = config.server.http;
155
+ if (opts.serverTimeouts.readTimeout) http.read_timeout = opts.serverTimeouts.readTimeout;
156
+ if (opts.serverTimeouts.writeTimeout) http.write_timeout = opts.serverTimeouts.writeTimeout;
157
+ if (opts.serverTimeouts.maxRequestSize) http.max_request_size = opts.serverTimeouts.maxRequestSize;
158
+ }
159
+ if (opts.logging) config.logging = { ...opts.logging };
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
+ };
166
+ if (opts.realPaths) sessionsObj.real_paths = true;
167
+ if (opts.sessions) {
168
+ if (opts.sessions.baseDir) sessionsObj.base_dir = opts.sessions.baseDir;
169
+ if (opts.sessions.maxSessions !== void 0) sessionsObj.max_sessions = opts.sessions.maxSessions;
170
+ if (opts.sessions.defaultTimeout) sessionsObj.default_timeout = opts.sessions.defaultTimeout;
171
+ if (opts.sessions.idleTimeout) sessionsObj.idle_timeout = opts.sessions.idleTimeout;
172
+ if (opts.sessions.cleanupInterval) sessionsObj.cleanup_interval = opts.sessions.cleanupInterval;
173
+ }
174
+ if (Object.keys(sessionsObj).length > 0) config.sessions = sessionsObj;
175
+ if (opts.audit) {
176
+ const auditObj = {};
177
+ if (opts.audit.enabled !== void 0) auditObj.enabled = opts.audit.enabled;
178
+ if (opts.audit.sqlitePath) auditObj.sqlite_path = opts.audit.sqlitePath;
179
+ config.audit = auditObj;
180
+ }
181
+ if (opts.sandboxLimits) {
182
+ config.sandbox.limits = {
183
+ ...opts.sandboxLimits.maxMemoryMb !== void 0 && { max_memory_mb: opts.sandboxLimits.maxMemoryMb },
184
+ ...opts.sandboxLimits.maxCpuPercent !== void 0 && { max_cpu_percent: opts.sandboxLimits.maxCpuPercent },
185
+ ...opts.sandboxLimits.maxProcesses !== void 0 && { max_processes: opts.sandboxLimits.maxProcesses }
186
+ };
187
+ }
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;
193
+ }
194
+ if (opts.networkIntercept) {
195
+ const net = config.sandbox.network;
196
+ if (opts.networkIntercept.interceptMode) net.intercept_mode = opts.networkIntercept.interceptMode;
197
+ if (opts.networkIntercept.proxyListenAddr) net.proxy_listen_addr = opts.networkIntercept.proxyListenAddr;
198
+ }
199
+ if (opts.seccompDetails) {
200
+ const sec = config.sandbox.seccomp;
201
+ if (!opts.ptrace?.enabled) sec.enabled = true;
202
+ if (opts.seccompDetails.execve !== void 0) sec.execve = opts.seccompDetails.execve;
203
+ if (opts.seccompDetails.fileMonitor) {
204
+ sec.file_monitor = {
205
+ ...opts.seccompDetails.fileMonitor.enabled !== void 0 && { enabled: opts.seccompDetails.fileMonitor.enabled },
206
+ ...opts.seccompDetails.fileMonitor.enforceWithoutFuse !== void 0 && { enforce_without_fuse: opts.seccompDetails.fileMonitor.enforceWithoutFuse }
207
+ };
208
+ }
209
+ }
210
+ if (opts.cgroups) {
211
+ config.sandbox.cgroups = { ...opts.cgroups };
212
+ }
213
+ if (opts.unixSockets) {
214
+ config.sandbox.unix_sockets = { ...opts.unixSockets };
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
+ }
242
+ if (opts.proxy) {
243
+ config.proxy = { ...opts.proxy };
244
+ }
245
+ if (opts.dlp) {
246
+ const dlpObj = {};
247
+ if (opts.dlp.mode) dlpObj.mode = opts.dlp.mode;
248
+ if (opts.dlp.patterns) dlpObj.patterns = opts.dlp.patterns;
249
+ if (opts.dlp.customPatterns) {
250
+ dlpObj.custom_patterns = opts.dlp.customPatterns.map((p) => ({
251
+ name: p.name,
252
+ display: p.display,
253
+ regex: p.regex
254
+ }));
255
+ }
256
+ config.dlp = dlpObj;
257
+ }
258
+ if (opts.policiesOverride) {
259
+ config.policies = {
260
+ ...opts.policiesOverride.dir && { dir: opts.policiesOverride.dir },
261
+ ...opts.policiesOverride.defaultPolicy && { default: opts.policiesOverride.defaultPolicy }
262
+ };
263
+ }
264
+ if (opts.approvals) config.approvals = { ...opts.approvals };
265
+ if (opts.metrics) config.metrics = { ...opts.metrics };
266
+ if (opts.health) {
267
+ const healthObj = {};
268
+ if (opts.health.path) healthObj.path = opts.health.path;
269
+ if (opts.health.readinessPath) healthObj.readiness_path = opts.health.readinessPath;
270
+ config.health = healthObj;
271
+ }
272
+ if (opts.development) {
273
+ const devObj = {};
274
+ if (opts.development.disableAuth !== void 0) devObj.disable_auth = opts.development.disableAuth;
275
+ if (opts.development.verboseErrors !== void 0) devObj.verbose_errors = opts.development.verboseErrors;
276
+ config.development = devObj;
277
+ }
139
278
  const feeds = opts.threatFeeds === false ? void 0 : opts.threatFeeds ?? defaultThreatFeeds;
140
279
  if (feeds) {
141
280
  config.threat_feeds = {
@@ -195,7 +334,8 @@ async function getTraceparent() {
195
334
 
196
335
  // src/core/provision.ts
197
336
  var SECURITY_MODE_RANK = {
198
- full: 4,
337
+ full: 5,
338
+ ptrace: 4,
199
339
  landlock: 3,
200
340
  "landlock-only": 2,
201
341
  minimal: 1
@@ -240,13 +380,23 @@ async function provision(adapter, config = {}) {
240
380
  traceParent,
241
381
  policyName = "policy",
242
382
  threatFeeds,
243
- packageChecks
383
+ packageChecks,
384
+ skipShim = false,
385
+ serverConfig: extendedConfig
244
386
  } = config;
245
387
  const policy = rawPolicy ? validatePolicy(rawPolicy) : agentDefault();
246
388
  let securityMode = "full";
247
389
  if (installStrategy === "running") {
248
390
  await healthCheck(adapter);
249
- 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
+ }
250
400
  if (minimumSecurityMode && isWeakerThan(securityMode, minimumSecurityMode)) {
251
401
  throw new ProvisioningError({
252
402
  phase: "install",
@@ -278,7 +428,15 @@ async function provision(adapter, config = {}) {
278
428
  });
279
429
  }
280
430
  } else if (installStrategy === "download" || installStrategy === "upload") {
281
- 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) {
282
440
  const arch = archOverride ?? await detectArch(adapter);
283
441
  if (installStrategy === "download") {
284
442
  await downloadBinary(adapter, agentshVersion, arch, agentshBinaryUrl);
@@ -323,28 +481,29 @@ async function provision(adapter, config = {}) {
323
481
  stderr: `Detected security mode '${securityMode}' is weaker than required '${minimumSecurityMode}'`
324
482
  });
325
483
  }
326
- const hasFuse = securityMode === "full" || securityMode === "landlock";
327
- const realPaths = realPathsOverride ?? hasFuse;
328
- const shimResult = await adapter.exec(
329
- "agentsh",
330
- [
331
- "shim",
332
- "install-shell",
333
- "--root",
334
- "/",
335
- "--shim",
336
- "/usr/bin/agentsh-shell-shim",
337
- "--bash",
338
- "--i-understand-this-modifies-the-host"
339
- ],
340
- { sudo: true }
341
- );
342
- if (shimResult.exitCode !== 0) {
343
- throw new ProvisioningError({
344
- phase: "install",
345
- command: "agentsh shim install-shell",
346
- stderr: shimResult.stderr
347
- });
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
+ }
348
507
  }
349
508
  const mkdirResult = await adapter.exec(
350
509
  "mkdir",
@@ -373,7 +532,8 @@ async function provision(adapter, config = {}) {
373
532
  watchtower,
374
533
  realPaths,
375
534
  threatFeeds,
376
- packageChecks
535
+ packageChecks,
536
+ ...extendedConfig
377
537
  });
378
538
  await adapter.writeFile("/etc/agentsh/config.yml", serverConfig, {
379
539
  sudo: true
@@ -414,7 +574,12 @@ async function provision(adapter, config = {}) {
414
574
  stderr: chownResult.stderr
415
575
  });
416
576
  }
417
- 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 });
418
583
  const serverResult = await adapter.exec(
419
584
  "agentsh",
420
585
  ["server", "--config", "/etc/agentsh/config.yml"],
@@ -459,6 +624,7 @@ async function provision(adapter, config = {}) {
459
624
  });
460
625
  }
461
626
  }
627
+ await adapter.exec("chmod", ["-R", "755", "/var/lib/agentsh/sessions/"], { sudo: true });
462
628
  const effectiveTraceParent = traceParent ?? await getTraceparent();
463
629
  if (effectiveTraceParent) {
464
630
  await adapter.exec("curl", [
@@ -605,7 +771,7 @@ async function detectSecurityMode(adapter) {
605
771
  });
606
772
  }
607
773
  const mode = parsed.security_mode;
608
- const validModes = ["full", "landlock", "landlock-only", "minimal"];
774
+ const validModes = ["full", "ptrace", "landlock", "landlock-only", "minimal"];
609
775
  if (!validModes.includes(mode)) {
610
776
  throw new ProvisioningError({
611
777
  phase: "install",