@capsulos/sdk 0.1.2 → 0.3.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/dist/capsule.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { DaemonClient, type ClientOptions } from './client.js';
2
2
  import { Ports } from './ports.js';
3
3
  import { PtySession } from './pty.js';
4
- import { type CapsuleSpec, type ExecOptions, type ExecResult, type FileEntry, type Health, type Provider, type PtyOptions } from './types.js';
4
+ import { type CapsuleSpec, type ExecOptions, type ExecResult, type FileEntry, type Health, type Provider, type PtyOptions, type SnapshotResult } from './types.js';
5
5
  export interface CapsuleOptions extends ClientOptions {
6
6
  /** How long to wait for the daemon to answer /health after creation. */
7
7
  readyTimeoutMs?: number;
@@ -14,6 +14,7 @@ export declare class Capsule {
14
14
  readonly id: string;
15
15
  private readonly provider;
16
16
  private readonly client;
17
+ private readonly readyTimeoutMs;
17
18
  readonly commands: Commands;
18
19
  readonly files: Files;
19
20
  readonly pty: Pty;
@@ -25,6 +26,20 @@ export declare class Capsule {
25
26
  /** Reconnect to a capsule that already exists (it may be suspended: the first request resumes it). */
26
27
  static connect(provider: Provider, id: string, opts?: CapsuleOptions): Promise<Capsule>;
27
28
  health(): Promise<Health>;
29
+ /**
30
+ * Freeze the capsule: processes stop, memory stays. Resume with `resume()`
31
+ * (or, on providers with auto-resume, with the next request). Throws when
32
+ * the provider has no explicit pause.
33
+ */
34
+ pause(): Promise<void>;
35
+ /** Wake a paused capsule and wait until the daemon answers again. */
36
+ resume(): Promise<void>;
37
+ /**
38
+ * Save the capsule's disk as a new template under `name`; `Capsule.create`
39
+ * with that template starts from this state. Not every provider can
40
+ * (Lambda MicroVMs cannot image a live VM).
41
+ */
42
+ snapshot(name: string): Promise<SnapshotResult>;
28
43
  /** Put the capsule back in your pocket. */
29
44
  kill(): Promise<void>;
30
45
  }
package/dist/capsule.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // The public face of a capsule. `Capsule.create()` asks the provider for a
2
2
  // machine and waits until capsulod answers; everything after that goes
3
3
  // straight to the daemon.
4
+ import { validateNetworkPolicy } from './network.js';
4
5
  import { DaemonClient } from './client.js';
5
6
  import { Ports } from './ports.js';
6
7
  import { PtySession } from './pty.js';
@@ -9,15 +10,17 @@ export class Capsule {
9
10
  id;
10
11
  provider;
11
12
  client;
13
+ readyTimeoutMs;
12
14
  commands;
13
15
  files;
14
16
  pty;
15
17
  /** Servers listening inside the capsule, reachable through the daemon (preview URLs). */
16
18
  ports;
17
- constructor(id, provider, client) {
19
+ constructor(id, provider, client, readyTimeoutMs = 120_000) {
18
20
  this.id = id;
19
21
  this.provider = provider;
20
22
  this.client = client;
23
+ this.readyTimeoutMs = readyTimeoutMs;
21
24
  this.commands = new Commands(client);
22
25
  this.files = new Files(client);
23
26
  this.pty = new Pty(client);
@@ -25,34 +28,76 @@ export class Capsule {
25
28
  }
26
29
  /** Throw a capsule: create it with the provider and wait until it is ready. */
27
30
  static async create(provider, spec, opts = {}) {
31
+ const network = validateNetworkPolicy(spec.network);
28
32
  const handle = await provider.create(spec, { timeoutMs: opts.createTimeoutMs, signal: opts.signal });
29
33
  const client = new DaemonClient(handle.connection, opts);
30
- const health = await waitReady(client, opts.readyTimeoutMs ?? 120_000, opts.signal);
31
- // Providers deliver the mount table through their boot hook. When that did
32
- // not happen (no hook, or the hook came without its payload), hand it over
33
- // directly. Either way, a mount that failed is an error here, not a
34
- // surprise later when a write lands in a plain directory.
35
- let results = health.mounts ?? [];
36
- const wanted = Object.keys(spec.mounts ?? {});
37
- const applied = new Set(results.map((r) => r.path));
38
- const hookMissed = !health.configured || wanted.some((p) => !applied.has(p));
39
- if (hookMissed && (spec.mounts || spec.metadata)) {
40
- results = await client.configure({ mounts: spec.mounts, metadata: spec.metadata });
34
+ try {
35
+ const health = await waitReady(client, opts.readyTimeoutMs ?? 120_000, opts.signal);
36
+ // Providers deliver mounts and network policy through their boot hook.
37
+ // When that did not happen (no hook, or the hook came without its
38
+ // payload, or the policy failed there), hand it over directly. Either
39
+ // way, a mount that failed or a policy that is not enforced is an error
40
+ // here, not a surprise later.
41
+ let results = health.mounts ?? [];
42
+ let status = health.network;
43
+ const wanted = Object.keys(spec.mounts ?? {});
44
+ const applied = new Set(results.map((r) => r.path));
45
+ const hookMissed = !health.configured || wanted.some((p) => !applied.has(p)) || (network && !status?.enforced);
46
+ if (hookMissed && (spec.mounts || spec.metadata || network)) {
47
+ ({ mounts: results, network: status } = await client.configure({ mounts: spec.mounts, metadata: spec.metadata, network }));
48
+ }
49
+ const failed = results.filter((r) => !r.ok);
50
+ if (failed.length) {
51
+ throw new CapsuleError(`mounts failed: ${failed.map((r) => `${r.path} (${r.driver}): ${r.error}`).join('; ')}`);
52
+ }
53
+ if (network && !status?.enforced) {
54
+ throw new CapsuleError(`network policy not enforced: ${status?.error ?? 'the daemon reported no policy (is it 0.3.0 or newer?)'}`);
55
+ }
41
56
  }
42
- const failed = results.filter((r) => !r.ok);
43
- if (failed.length) {
44
- throw new CapsuleError(`mounts failed: ${failed.map((r) => `${r.path} (${r.driver}): ${r.error}`).join('; ')}`);
57
+ catch (err) {
58
+ // A capsule that did not get its spec is not handed out: it goes, and
59
+ // the error says which one it was.
60
+ await provider.terminate(handle.id).catch(() => { });
61
+ const e = err instanceof CapsuleError ? err : new CapsuleError(err instanceof Error ? err.message : String(err));
62
+ throw new CapsuleError(`${e.message} (capsule ${handle.id} terminated)`, e.status, { capsuleId: handle.id });
45
63
  }
46
- return new Capsule(handle.id, provider, client);
64
+ return new Capsule(handle.id, provider, client, opts.readyTimeoutMs);
47
65
  }
48
66
  /** Reconnect to a capsule that already exists (it may be suspended: the first request resumes it). */
49
67
  static async connect(provider, id, opts = {}) {
50
68
  const conn = await provider.connect(id);
51
- return new Capsule(id, provider, new DaemonClient(conn, opts));
69
+ return new Capsule(id, provider, new DaemonClient(conn, opts), opts.readyTimeoutMs);
52
70
  }
53
71
  health() {
54
72
  return this.client.health();
55
73
  }
74
+ /**
75
+ * Freeze the capsule: processes stop, memory stays. Resume with `resume()`
76
+ * (or, on providers with auto-resume, with the next request). Throws when
77
+ * the provider has no explicit pause.
78
+ */
79
+ async pause() {
80
+ if (!this.provider.pause)
81
+ throw unsupported(this.provider, 'pause');
82
+ await this.provider.pause(this.id);
83
+ }
84
+ /** Wake a paused capsule and wait until the daemon answers again. */
85
+ async resume() {
86
+ if (!this.provider.resume)
87
+ throw unsupported(this.provider, 'resume');
88
+ await this.provider.resume(this.id);
89
+ await waitReady(this.client, this.readyTimeoutMs);
90
+ }
91
+ /**
92
+ * Save the capsule's disk as a new template under `name`; `Capsule.create`
93
+ * with that template starts from this state. Not every provider can
94
+ * (Lambda MicroVMs cannot image a live VM).
95
+ */
96
+ async snapshot(name) {
97
+ if (!this.provider.snapshot)
98
+ throw unsupported(this.provider, 'snapshot');
99
+ return this.provider.snapshot(this.id, name);
100
+ }
56
101
  /** Put the capsule back in your pocket. */
57
102
  async kill() {
58
103
  await this.provider.terminate(this.id);
@@ -105,6 +150,9 @@ export class Files {
105
150
  return this.client.remove(path);
106
151
  }
107
152
  }
153
+ function unsupported(provider, op) {
154
+ return new CapsuleError(`the ${provider.name} provider does not support ${op}()`);
155
+ }
108
156
  async function waitReady(client, timeoutMs, signal) {
109
157
  const t0 = Date.now();
110
158
  let lastErr;
@@ -1 +1 @@
1
- {"version":3,"file":"capsule.js","sourceRoot":"","sources":["../src/capsule.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"capsule.js","sourceRoot":"","sources":["../src/capsule.ts"],"names":[],"mappings":"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"}
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Connection, type ExecEvent, type ExecOptions, type ExecResult, type FileEntry, type Health, type MountResult, type MountSpec } from './types.js';
1
+ import { type Connection, type ExecEvent, type ExecOptions, type ExecResult, type FileEntry, type Health, type MountResult, type MountSpec, type NetworkPolicy, type NetworkStatus } from './types.js';
2
2
  export interface ClientOptions {
3
3
  fetch?: typeof fetch;
4
4
  requestTimeoutMs?: number;
@@ -10,11 +10,15 @@ export declare class DaemonClient {
10
10
  constructor(connection: Connection, opts?: ClientOptions);
11
11
  private request;
12
12
  health(): Promise<Health>;
13
- /** Apply the mount table when the provider's boot hook did not deliver it. */
13
+ /** Apply the mount table and network policy when the provider's boot hook did not deliver them. */
14
14
  configure(body: {
15
15
  mounts?: Record<string, MountSpec>;
16
16
  metadata?: Record<string, string>;
17
- }): Promise<MountResult[]>;
17
+ network?: NetworkPolicy;
18
+ }): Promise<{
19
+ mounts: MountResult[];
20
+ network?: NetworkStatus;
21
+ }>;
18
22
  /** Buffered or streamed depending on whether callbacks are given. */
19
23
  exec(cmd: string, opts?: ExecOptions): Promise<ExecResult>;
20
24
  readFile(path: string): Promise<Uint8Array>;
package/dist/client.js CHANGED
@@ -37,7 +37,7 @@ export class DaemonClient {
37
37
  const res = await this.request('/health', { timeoutMs: 10_000 });
38
38
  return (await res.json());
39
39
  }
40
- /** Apply the mount table when the provider's boot hook did not deliver it. */
40
+ /** Apply the mount table and network policy when the provider's boot hook did not deliver them. */
41
41
  async configure(body) {
42
42
  const res = await this.request('/configure', {
43
43
  method: 'POST',
@@ -45,7 +45,8 @@ export class DaemonClient {
45
45
  headers: { 'Content-Type': 'application/json' },
46
46
  timeoutMs: 60_000,
47
47
  });
48
- return (await res.json()).mounts ?? [];
48
+ const out = (await res.json());
49
+ return { mounts: out.mounts ?? [], network: out.network };
49
50
  }
50
51
  /** Buffered or streamed depending on whether callbacks are given. */
51
52
  async exec(cmd, opts = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,gFAAgF;AAChF,OAAO,EACL,YAAY,GASb,MAAM,YAAY,CAAC;AAOpB,MAAM,OAAO,YAAY;IAKZ;IAJM,SAAS,CAAe;IACxB,gBAAgB,CAAS;IAE1C,YACW,UAAsB,EAC/B,OAAsB,EAAE;QADf,eAAU,GAAV,UAAU,CAAY;QAG/B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,MAAM,CAAC;IAC1D,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,OAA6C,EAAE,EAAE,KAAK,GAAG,KAAK;QAChG,MAAM,OAAO,GAAG,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,GAAI,IAAI,CAAC,OAA8C,EAAE,CAAC;QAC3H,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE;YACpE,GAAG,IAAI;YACP,OAAO;YACP,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,gBAAgB,CAAC;SACrE,CAAC,CAAC;QACH,6FAA6F;QAC7F,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACzD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACxC,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,MAAM,GAAI,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAwB,CAAC,KAAK,IAAI,EAAE,CAAC;YAClE,CAAC;YAAC,MAAM,CAAC;gBACP,yBAAyB;YAC3B,CAAC;YACD,MAAM,IAAI,YAAY,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,IAAI,IAAI,WAAW,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3H,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAW,CAAC;IACtC,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,SAAS,CAAC,IAA+E;QAC7F,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE;YAC3C,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC1B,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,SAAS,EAAE,MAAM;SAClB,CAAC,CAAC;QACH,OAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA+B,CAAC,MAAM,IAAI,EAAE,CAAC;IACxE,CAAC;IAED,qEAAqE;IACrE,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,OAAoB,EAAE;QAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;YAC1B,GAAG;YACH,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;QACH,+EAA+E;QAC/E,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC;QACtD,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE1D,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;YAC9H,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAe,CAAC;QAC1C,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;YACtC,MAAM,EAAE,MAAM;YACd,IAAI;YACJ,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,EAAE,mBAAmB,EAAE;YAC5E,SAAS;SACV,CAAC,CAAC;QACH,MAAM,MAAM,GAAe,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QACnG,IAAI,KAAK,EAAE,MAAM,EAAE,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACrC,IAAI,EAAE,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACzB,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,IAAI,CAAC;gBACzB,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC;iBAAM,IAAI,EAAE,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAChC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,IAAI,CAAC;gBACzB,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,IAAI,CAAC,CAAC;gBACnC,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC,OAAO,IAAI,KAAK,CAAC;gBACrC,IAAI,EAAE,CAAC,KAAK;oBAAE,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC;YACxC,CAAC;QACH,CAAC;QACD,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QAC3C,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY;QACzB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,oBAAoB,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/E,OAAO,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY;QACzB,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAY,EAAE,IAAyB,EAAE,OAA0B,EAAE;QACnF,MAAM,CAAC,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACnF,MAAM,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,EAAE;YACtC,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,IAAiB;YAC1D,OAAO,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE;SACxD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,oBAAoB,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/E,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAgB,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,oBAAoB,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/E,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAc,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,MAAM,IAAI,CAAC,OAAO,CAAC,qBAAqB,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,MAAM,IAAI,CAAC,OAAO,CAAC,eAAe,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtF,CAAC;CACF;AAED,qDAAqD;AACrD,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,QAAQ,CAAC,GAAa;IAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC;IACrC,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,YAAY,CAAC,yBAAyB,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,SAAS,CAAC;QACR,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,IAAI;YAAE,MAAM;QAChB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,IAAI,GAAW,CAAC;QAChB,OAAO,CAAC,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACnC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;YACvE,IAAI,QAAQ;gBAAE,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAc,CAAC;QACjE,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,3 +5,4 @@ export type { CapsuleOptions } from './capsule.js';
5
5
  export { DaemonClient, parseSse } from './client.js';
6
6
  export type { ClientOptions } from './client.js';
7
7
  export * from './types.js';
8
+ export { validateNetworkPolicy } from './network.js';
package/dist/index.js CHANGED
@@ -3,4 +3,5 @@ export { Ports } from './ports.js';
3
3
  export { PtySession } from './pty.js';
4
4
  export { DaemonClient, parseSse } from './client.js';
5
5
  export * from './types.js';
6
+ export { validateNetworkPolicy } from './network.js';
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAErD,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAErD,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type NetworkPolicy } from './types.js';
2
+ /**
3
+ * Check a network policy before a capsule is created with it (the daemon
4
+ * applies the same rules; failing here saves a launch). Returns the policy
5
+ * when it restricts something, undefined when it does not.
6
+ */
7
+ export declare function validateNetworkPolicy(policy: NetworkPolicy | undefined): NetworkPolicy | undefined;
@@ -0,0 +1,37 @@
1
+ import { CapsuleError } from './types.js';
2
+ /**
3
+ * Check a network policy before a capsule is created with it (the daemon
4
+ * applies the same rules; failing here saves a launch). Returns the policy
5
+ * when it restricts something, undefined when it does not.
6
+ */
7
+ export function validateNetworkPolicy(policy) {
8
+ if (!policy)
9
+ return undefined;
10
+ if (policy.internet !== 'allow' && policy.internet !== 'deny') {
11
+ throw new CapsuleError(`network.internet must be 'allow' or 'deny', got ${JSON.stringify(policy.internet)}`);
12
+ }
13
+ if (policy.internet === 'allow') {
14
+ if (policy.allow?.length)
15
+ throw new CapsuleError("network.allow needs network.internet: 'deny'");
16
+ return undefined;
17
+ }
18
+ for (const entry of policy.allow ?? []) {
19
+ if (!isAllowEntry(entry)) {
20
+ throw new CapsuleError(`bad network.allow entry ${JSON.stringify(entry)} (want host, *.domain or host:port)`);
21
+ }
22
+ }
23
+ return policy;
24
+ }
25
+ const HOST = /^(\*\.)?[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
26
+ function isAllowEntry(entry) {
27
+ if (typeof entry !== 'string')
28
+ return false;
29
+ const m = /^(.*?)(?::(\d{1,5}))?$/.exec(entry.trim());
30
+ if (!m)
31
+ return false;
32
+ const [, host, port] = m;
33
+ if (port !== undefined && (Number(port) < 1 || Number(port) > 65535))
34
+ return false;
35
+ return host !== undefined && HOST.test(host);
36
+ }
37
+ //# sourceMappingURL=network.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network.js","sourceRoot":"","sources":["../src/network.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAsB,MAAM,YAAY,CAAC;AAE9D;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAiC;IACrE,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QAC9D,MAAM,IAAI,YAAY,CAAC,mDAAmD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC/G,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM;YAAE,MAAM,IAAI,YAAY,CAAC,8CAA8C,CAAC,CAAC;QACjG,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,YAAY,CAAC,2BAA2B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QAChH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,IAAI,GAAG,2EAA2E,CAAC;AAEzF,SAAS,YAAY,CAAC,KAAa;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,CAAC,GAAG,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IACzB,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnF,OAAO,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/C,CAAC"}
package/dist/types.d.ts CHANGED
@@ -9,6 +9,25 @@ export interface Provider {
9
9
  terminate(id: string): Promise<void>;
10
10
  /** How to reach the daemon of an existing capsule (endpoint + auth headers). */
11
11
  connect(id: string): Promise<Connection>;
12
+ /**
13
+ * Freeze the capsule now (memory kept, no CPU, no billing for compute on
14
+ * providers that stop charging). Optional: a provider without it leaves
15
+ * suspension to its idle policy.
16
+ */
17
+ pause?(id: string): Promise<void>;
18
+ /** Wake a paused capsule and return when the provider reports it running (the daemon may need a moment more). */
19
+ resume?(id: string): Promise<void>;
20
+ /**
21
+ * Save the capsule's current disk as a new template, usable in `create()`.
22
+ * Optional: Lambda MicroVMs cannot image a live VM (images are built from
23
+ * a zip), Docker can (`docker commit`).
24
+ */
25
+ snapshot?(id: string, name: string): Promise<SnapshotResult>;
26
+ }
27
+ export interface SnapshotResult {
28
+ /** The template name to pass to `create()` on the same provider. */
29
+ template: string;
30
+ provider: string;
12
31
  }
13
32
  export interface CreateOptions {
14
33
  /**
@@ -81,6 +100,7 @@ export interface Health {
81
100
  /** True once the mount table was applied (by the boot hook or /configure). */
82
101
  configured: boolean;
83
102
  mounts?: MountResult[];
103
+ network?: NetworkStatus;
84
104
  /** The template's long-running process (CAPSULOD_START), when the template declares one. */
85
105
  service?: ServiceStatus;
86
106
  }
@@ -92,6 +112,33 @@ export interface ServiceStatus {
92
112
  lastExitCode?: number;
93
113
  startedAt?: string;
94
114
  }
115
+ /**
116
+ * What the capsule may reach. With `internet: 'deny'` the daemon runs an
117
+ * egress proxy limited to `allow` (HTTP_PROXY/HTTPS_PROXY point at it for
118
+ * every process) and blackholes the default route, so a process that ignores
119
+ * the proxy gets "network unreachable" rather than the internet. Needs
120
+ * CAP_NET_ADMIN in the capsule: Lambda templates built with
121
+ * `--capabilities all`; the Docker provider adds it. Creation fails if the
122
+ * policy cannot be enforced.
123
+ */
124
+ export interface NetworkPolicy {
125
+ /** 'allow' (default): unrestricted. 'deny': nothing leaves except `allow`. */
126
+ internet: 'allow' | 'deny';
127
+ /** Destinations still reachable with 'deny': 'host', '*.domain' (subdomains only) or 'host:port'. */
128
+ allow?: string[];
129
+ }
130
+ /** The daemon's view of the policy (`health().network`; absent without a policy). */
131
+ export interface NetworkStatus {
132
+ internet: 'allow' | 'deny';
133
+ allow?: string[];
134
+ /** True once the proxy runs and the routes are locked. */
135
+ enforced: boolean;
136
+ /** The egress proxy's address inside the capsule (127.0.0.1:3128). */
137
+ proxy?: string;
138
+ /** Connections refused so far. */
139
+ blocked: number;
140
+ error?: string;
141
+ }
95
142
  export interface IdlePolicy {
96
143
  /** Suspend after this long without traffic (seconds). */
97
144
  suspendAfterSeconds: number;
@@ -107,6 +154,8 @@ export interface CapsuleSpec {
107
154
  mounts?: Record<string, MountSpec>;
108
155
  /** Opaque per-capsule configuration delivered to the daemon at boot (never secrets). */
109
156
  metadata?: Record<string, string>;
157
+ /** Egress policy, enforced inside the capsule by the daemon (default: unrestricted). */
158
+ network?: NetworkPolicy;
110
159
  /** Idempotency key: creating twice with the same key returns the same capsule. */
111
160
  clientToken?: string;
112
161
  }
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,0EAA0E;AAC1E,kEAAkE;AAClE,gFAAgF;AAwKhF,MAAM,OAAO,YAAa,SAAQ,KAAK;IAM1B;IALX,sGAAsG;IAC7F,SAAS,CAAU;IAE5B,YACE,OAAe,EACN,MAAe,EACxB,OAA+B,EAAE;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,WAAM,GAAN,MAAM,CAAS;QAIxB,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,IAAI,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IACtD,CAAC;CACF"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,0EAA0E;AAC1E,kEAAkE;AAClE,gFAAgF;AA4NhF,MAAM,OAAO,YAAa,SAAQ,KAAK;IAM1B;IALX,sGAAsG;IAC7F,SAAS,CAAU;IAE5B,YACE,OAAe,EACN,MAAe,EACxB,OAA+B,EAAE;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,WAAM,GAAN,MAAM,CAAS;QAIxB,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,IAAI,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IACtD,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capsulos/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "Throw a capsule, get a computer. Sandboxes for AI agents on your own AWS account (Lambda MicroVMs) or on Docker.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Capsule } from './capsule.js';
3
+ import type { Provider } from './types.js';
4
+
5
+ const bare: Provider = {
6
+ name: 'bare',
7
+ async create() {
8
+ throw new Error('unused');
9
+ },
10
+ async get() {
11
+ return null;
12
+ },
13
+ async list() {
14
+ return [];
15
+ },
16
+ async terminate() {},
17
+ async connect() {
18
+ return { baseUrl: 'http://capsule', headers: async () => ({}) };
19
+ },
20
+ };
21
+
22
+ describe('Capsule optional operations', () => {
23
+ it('names the provider and the operation when it is not supported', async () => {
24
+ const capsule = await Capsule.connect(bare, 'c1');
25
+ await expect(capsule.pause()).rejects.toThrow(/bare provider does not support pause\(\)/);
26
+ await expect(capsule.resume()).rejects.toThrow(/does not support resume\(\)/);
27
+ await expect(capsule.snapshot('x')).rejects.toThrow(/does not support snapshot\(\)/);
28
+ });
29
+
30
+ it('delegates pause and snapshot to the provider', async () => {
31
+ const calls: string[] = [];
32
+ const p: Provider = {
33
+ ...bare,
34
+ name: 'full',
35
+ async pause(id) {
36
+ calls.push(`pause:${id}`);
37
+ },
38
+ async snapshot(id, name) {
39
+ calls.push(`snapshot:${id}:${name}`);
40
+ return { template: name, provider: 'full' };
41
+ },
42
+ };
43
+ const capsule = await Capsule.connect(p, 'c1');
44
+ await capsule.pause();
45
+ expect(await capsule.snapshot('tpl')).toEqual({ template: 'tpl', provider: 'full' });
46
+ expect(calls).toEqual(['pause:c1', 'snapshot:c1:tpl']);
47
+ });
48
+ });
package/src/capsule.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // The public face of a capsule. `Capsule.create()` asks the provider for a
2
2
  // machine and waits until capsulod answers; everything after that goes
3
3
  // straight to the daemon.
4
+ import { validateNetworkPolicy } from './network.js';
4
5
  import { DaemonClient, type ClientOptions } from './client.js';
5
6
  import { Ports } from './ports.js';
6
7
  import { PtySession } from './pty.js';
@@ -13,6 +14,7 @@ import {
13
14
  type Health,
14
15
  type Provider,
15
16
  type PtyOptions,
17
+ type SnapshotResult,
16
18
  } from './types.js';
17
19
 
18
20
  export interface CapsuleOptions extends ClientOptions {
@@ -35,6 +37,7 @@ export class Capsule {
35
37
  readonly id: string,
36
38
  private readonly provider: Provider,
37
39
  private readonly client: DaemonClient,
40
+ private readonly readyTimeoutMs = 120_000,
38
41
  ) {
39
42
  this.commands = new Commands(client);
40
43
  this.files = new Files(client);
@@ -44,37 +47,78 @@ export class Capsule {
44
47
 
45
48
  /** Throw a capsule: create it with the provider and wait until it is ready. */
46
49
  static async create(provider: Provider, spec: CapsuleSpec, opts: CapsuleOptions = {}): Promise<Capsule> {
50
+ const network = validateNetworkPolicy(spec.network);
47
51
  const handle = await provider.create(spec, { timeoutMs: opts.createTimeoutMs, signal: opts.signal });
48
52
  const client = new DaemonClient(handle.connection, opts);
49
- const health = await waitReady(client, opts.readyTimeoutMs ?? 120_000, opts.signal);
50
- // Providers deliver the mount table through their boot hook. When that did
51
- // not happen (no hook, or the hook came without its payload), hand it over
52
- // directly. Either way, a mount that failed is an error here, not a
53
- // surprise later when a write lands in a plain directory.
54
- let results = health.mounts ?? [];
55
- const wanted = Object.keys(spec.mounts ?? {});
56
- const applied = new Set(results.map((r) => r.path));
57
- const hookMissed = !health.configured || wanted.some((p) => !applied.has(p));
58
- if (hookMissed && (spec.mounts || spec.metadata)) {
59
- results = await client.configure({ mounts: spec.mounts, metadata: spec.metadata });
60
- }
61
- const failed = results.filter((r) => !r.ok);
62
- if (failed.length) {
63
- throw new CapsuleError(`mounts failed: ${failed.map((r) => `${r.path} (${r.driver}): ${r.error}`).join('; ')}`);
53
+ try {
54
+ const health = await waitReady(client, opts.readyTimeoutMs ?? 120_000, opts.signal);
55
+ // Providers deliver mounts and network policy through their boot hook.
56
+ // When that did not happen (no hook, or the hook came without its
57
+ // payload, or the policy failed there), hand it over directly. Either
58
+ // way, a mount that failed or a policy that is not enforced is an error
59
+ // here, not a surprise later.
60
+ let results = health.mounts ?? [];
61
+ let status = health.network;
62
+ const wanted = Object.keys(spec.mounts ?? {});
63
+ const applied = new Set(results.map((r) => r.path));
64
+ const hookMissed = !health.configured || wanted.some((p) => !applied.has(p)) || (network && !status?.enforced);
65
+ if (hookMissed && (spec.mounts || spec.metadata || network)) {
66
+ ({ mounts: results, network: status } = await client.configure({ mounts: spec.mounts, metadata: spec.metadata, network }));
67
+ }
68
+ const failed = results.filter((r) => !r.ok);
69
+ if (failed.length) {
70
+ throw new CapsuleError(`mounts failed: ${failed.map((r) => `${r.path} (${r.driver}): ${r.error}`).join('; ')}`);
71
+ }
72
+ if (network && !status?.enforced) {
73
+ throw new CapsuleError(`network policy not enforced: ${status?.error ?? 'the daemon reported no policy (is it 0.3.0 or newer?)'}`);
74
+ }
75
+ } catch (err) {
76
+ // A capsule that did not get its spec is not handed out: it goes, and
77
+ // the error says which one it was.
78
+ await provider.terminate(handle.id).catch(() => {});
79
+ const e = err instanceof CapsuleError ? err : new CapsuleError(err instanceof Error ? err.message : String(err));
80
+ throw new CapsuleError(`${e.message} (capsule ${handle.id} terminated)`, e.status, { capsuleId: handle.id });
64
81
  }
65
- return new Capsule(handle.id, provider, client);
82
+ return new Capsule(handle.id, provider, client, opts.readyTimeoutMs);
66
83
  }
67
84
 
68
85
  /** Reconnect to a capsule that already exists (it may be suspended: the first request resumes it). */
69
86
  static async connect(provider: Provider, id: string, opts: CapsuleOptions = {}): Promise<Capsule> {
70
87
  const conn = await provider.connect(id);
71
- return new Capsule(id, provider, new DaemonClient(conn, opts));
88
+ return new Capsule(id, provider, new DaemonClient(conn, opts), opts.readyTimeoutMs);
72
89
  }
73
90
 
74
91
  health() {
75
92
  return this.client.health();
76
93
  }
77
94
 
95
+ /**
96
+ * Freeze the capsule: processes stop, memory stays. Resume with `resume()`
97
+ * (or, on providers with auto-resume, with the next request). Throws when
98
+ * the provider has no explicit pause.
99
+ */
100
+ async pause(): Promise<void> {
101
+ if (!this.provider.pause) throw unsupported(this.provider, 'pause');
102
+ await this.provider.pause(this.id);
103
+ }
104
+
105
+ /** Wake a paused capsule and wait until the daemon answers again. */
106
+ async resume(): Promise<void> {
107
+ if (!this.provider.resume) throw unsupported(this.provider, 'resume');
108
+ await this.provider.resume(this.id);
109
+ await waitReady(this.client, this.readyTimeoutMs);
110
+ }
111
+
112
+ /**
113
+ * Save the capsule's disk as a new template under `name`; `Capsule.create`
114
+ * with that template starts from this state. Not every provider can
115
+ * (Lambda MicroVMs cannot image a live VM).
116
+ */
117
+ async snapshot(name: string): Promise<SnapshotResult> {
118
+ if (!this.provider.snapshot) throw unsupported(this.provider, 'snapshot');
119
+ return this.provider.snapshot(this.id, name);
120
+ }
121
+
78
122
  /** Put the capsule back in your pocket. */
79
123
  async kill(): Promise<void> {
80
124
  await this.provider.terminate(this.id);
@@ -122,6 +166,10 @@ export class Files {
122
166
  }
123
167
  }
124
168
 
169
+ function unsupported(provider: Provider, op: string): CapsuleError {
170
+ return new CapsuleError(`the ${provider.name} provider does not support ${op}()`);
171
+ }
172
+
125
173
  async function waitReady(client: DaemonClient, timeoutMs: number, signal?: AbortSignal): Promise<Health> {
126
174
  const t0 = Date.now();
127
175
  let lastErr: unknown;
package/src/client.ts CHANGED
@@ -10,6 +10,8 @@ import {
10
10
  type Health,
11
11
  type MountResult,
12
12
  type MountSpec,
13
+ type NetworkPolicy,
14
+ type NetworkStatus,
13
15
  } from './types.js';
14
16
 
15
17
  export interface ClientOptions {
@@ -57,15 +59,20 @@ export class DaemonClient {
57
59
  return (await res.json()) as Health;
58
60
  }
59
61
 
60
- /** Apply the mount table when the provider's boot hook did not deliver it. */
61
- async configure(body: { mounts?: Record<string, MountSpec>; metadata?: Record<string, string> }): Promise<MountResult[]> {
62
+ /** Apply the mount table and network policy when the provider's boot hook did not deliver them. */
63
+ async configure(body: {
64
+ mounts?: Record<string, MountSpec>;
65
+ metadata?: Record<string, string>;
66
+ network?: NetworkPolicy;
67
+ }): Promise<{ mounts: MountResult[]; network?: NetworkStatus }> {
62
68
  const res = await this.request('/configure', {
63
69
  method: 'POST',
64
70
  body: JSON.stringify(body),
65
71
  headers: { 'Content-Type': 'application/json' },
66
72
  timeoutMs: 60_000,
67
73
  });
68
- return ((await res.json()) as { mounts: MountResult[] }).mounts ?? [];
74
+ const out = (await res.json()) as { mounts?: MountResult[]; network?: NetworkStatus };
75
+ return { mounts: out.mounts ?? [], network: out.network };
69
76
  }
70
77
 
71
78
  /** Buffered or streamed depending on whether callbacks are given. */
package/src/index.ts CHANGED
@@ -5,3 +5,4 @@ export type { CapsuleOptions } from './capsule.js';
5
5
  export { DaemonClient, parseSse } from './client.js';
6
6
  export type { ClientOptions } from './client.js';
7
7
  export * from './types.js';
8
+ export { validateNetworkPolicy } from './network.js';
@@ -0,0 +1,101 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Capsule } from './capsule.js';
3
+ import { validateNetworkPolicy } from './network.js';
4
+ import type { CapsuleHandle, Provider } from './types.js';
5
+
6
+ describe('validateNetworkPolicy', () => {
7
+ it('accepts hosts, wildcards and host:port, and treats allow-all as no policy', () => {
8
+ expect(validateNetworkPolicy(undefined)).toBeUndefined();
9
+ expect(validateNetworkPolicy({ internet: 'allow' })).toBeUndefined();
10
+ const p = { internet: 'deny' as const, allow: ['api.anthropic.com', '*.npmjs.org', 'db.internal:5432', 'Host.IO'] };
11
+ expect(validateNetworkPolicy(p)).toBe(p);
12
+ expect(validateNetworkPolicy({ internet: 'deny' })).toBeDefined();
13
+ });
14
+
15
+ it('rejects what the daemon would reject, before launching anything', () => {
16
+ expect(() => validateNetworkPolicy({ internet: 'maybe' as 'allow' })).toThrow(/must be 'allow' or 'deny'/);
17
+ expect(() => validateNetworkPolicy({ internet: 'allow', allow: ['x.com'] })).toThrow(/needs network.internet: 'deny'/);
18
+ for (const bad of ['https://x.com', '*', 'a.*.com', 'x.com:99999', 'x.com/path', '', 'x com']) {
19
+ expect(() => validateNetworkPolicy({ internet: 'deny', allow: [bad] }), bad).toThrow(/bad network.allow entry/);
20
+ }
21
+ });
22
+ });
23
+
24
+ function fakeProvider(health: Record<string, unknown>, configure: Record<string, unknown> | Error) {
25
+ const calls: string[] = [];
26
+ const fetchStub: typeof fetch = async (input, init) => {
27
+ const path = new URL(String(input)).pathname;
28
+ calls.push(`${init?.method ?? 'GET'} ${path}`);
29
+ if (path === '/health') return new Response(JSON.stringify({ ok: true, ...health }), { status: 200 });
30
+ if (configure instanceof Error) return new Response(JSON.stringify({ error: configure.message }), { status: 400 });
31
+ return new Response(JSON.stringify(configure), { status: 200 });
32
+ };
33
+ const handle: CapsuleHandle = {
34
+ id: 'c1',
35
+ provider: 'fake',
36
+ state: 'running',
37
+ template: 't',
38
+ createdAt: new Date(),
39
+ connection: { baseUrl: 'http://capsule', headers: async () => ({}) },
40
+ };
41
+ const provider: Provider = {
42
+ name: 'fake',
43
+ async create() {
44
+ calls.push('create');
45
+ return handle;
46
+ },
47
+ async get() {
48
+ return null;
49
+ },
50
+ async list() {
51
+ return [];
52
+ },
53
+ async terminate(id) {
54
+ calls.push(`terminate ${id}`);
55
+ },
56
+ async connect() {
57
+ return handle.connection;
58
+ },
59
+ };
60
+ return { provider, calls, fetch: fetchStub };
61
+ }
62
+
63
+ describe('Capsule.create with a network policy', () => {
64
+ const spec = { template: 't', network: { internet: 'deny' as const, allow: ['api.anthropic.com'] } };
65
+
66
+ it('is satisfied when the boot hook already enforced it', async () => {
67
+ const { provider, calls, fetch } = fakeProvider({ configured: true, network: { internet: 'deny', enforced: true, blocked: 0 } }, {});
68
+ const capsule = await Capsule.create(provider, spec, { fetch });
69
+ expect(capsule.id).toBe('c1');
70
+ expect(calls).toEqual(['create', 'GET /health']);
71
+ });
72
+
73
+ it('hands the policy over through /configure when the hook did not apply it', async () => {
74
+ const { provider, calls, fetch } = fakeProvider(
75
+ { configured: false },
76
+ { ok: true, mounts: [], network: { internet: 'deny', enforced: true, blocked: 0 } },
77
+ );
78
+ await Capsule.create(provider, spec, { fetch });
79
+ expect(calls).toEqual(['create', 'GET /health', 'POST /configure']);
80
+ });
81
+
82
+ it('terminates the capsule and says why when the policy cannot be enforced', async () => {
83
+ const { provider, calls, fetch } = fakeProvider(
84
+ { configured: true, network: { internet: 'deny', enforced: false, blocked: 0, error: 'network policy needs CAP_NET_ADMIN' } },
85
+ new Error('network policy needs CAP_NET_ADMIN: build the template with --capabilities all'),
86
+ );
87
+ const err = (await Capsule.create(provider, spec, { fetch }).catch((e: unknown) => e)) as Error & { capsuleId?: string };
88
+ expect(err.message).toMatch(/CAP_NET_ADMIN/);
89
+ expect(err.message).toMatch(/capsule c1 terminated/);
90
+ expect(err.capsuleId).toBe('c1');
91
+ expect(calls.at(-1)).toBe('terminate c1');
92
+ });
93
+
94
+ it('rejects a bad policy before creating anything', async () => {
95
+ const { provider, calls, fetch } = fakeProvider({}, {});
96
+ await expect(Capsule.create(provider, { template: 't', network: { internet: 'deny', allow: ['*'] } }, { fetch })).rejects.toThrow(
97
+ /bad network.allow entry/,
98
+ );
99
+ expect(calls).toEqual([]);
100
+ });
101
+ });
package/src/network.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { CapsuleError, type NetworkPolicy } from './types.js';
2
+
3
+ /**
4
+ * Check a network policy before a capsule is created with it (the daemon
5
+ * applies the same rules; failing here saves a launch). Returns the policy
6
+ * when it restricts something, undefined when it does not.
7
+ */
8
+ export function validateNetworkPolicy(policy: NetworkPolicy | undefined): NetworkPolicy | undefined {
9
+ if (!policy) return undefined;
10
+ if (policy.internet !== 'allow' && policy.internet !== 'deny') {
11
+ throw new CapsuleError(`network.internet must be 'allow' or 'deny', got ${JSON.stringify(policy.internet)}`);
12
+ }
13
+ if (policy.internet === 'allow') {
14
+ if (policy.allow?.length) throw new CapsuleError("network.allow needs network.internet: 'deny'");
15
+ return undefined;
16
+ }
17
+ for (const entry of policy.allow ?? []) {
18
+ if (!isAllowEntry(entry)) {
19
+ throw new CapsuleError(`bad network.allow entry ${JSON.stringify(entry)} (want host, *.domain or host:port)`);
20
+ }
21
+ }
22
+ return policy;
23
+ }
24
+
25
+ const HOST = /^(\*\.)?[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
26
+
27
+ function isAllowEntry(entry: string): boolean {
28
+ if (typeof entry !== 'string') return false;
29
+ const m = /^(.*?)(?::(\d{1,5}))?$/.exec(entry.trim());
30
+ if (!m) return false;
31
+ const [, host, port] = m;
32
+ if (port !== undefined && (Number(port) < 1 || Number(port) > 65535)) return false;
33
+ return host !== undefined && HOST.test(host);
34
+ }
package/src/types.ts CHANGED
@@ -14,6 +14,26 @@ export interface Provider {
14
14
  terminate(id: string): Promise<void>;
15
15
  /** How to reach the daemon of an existing capsule (endpoint + auth headers). */
16
16
  connect(id: string): Promise<Connection>;
17
+ /**
18
+ * Freeze the capsule now (memory kept, no CPU, no billing for compute on
19
+ * providers that stop charging). Optional: a provider without it leaves
20
+ * suspension to its idle policy.
21
+ */
22
+ pause?(id: string): Promise<void>;
23
+ /** Wake a paused capsule and return when the provider reports it running (the daemon may need a moment more). */
24
+ resume?(id: string): Promise<void>;
25
+ /**
26
+ * Save the capsule's current disk as a new template, usable in `create()`.
27
+ * Optional: Lambda MicroVMs cannot image a live VM (images are built from
28
+ * a zip), Docker can (`docker commit`).
29
+ */
30
+ snapshot?(id: string, name: string): Promise<SnapshotResult>;
31
+ }
32
+
33
+ export interface SnapshotResult {
34
+ /** The template name to pass to `create()` on the same provider. */
35
+ template: string;
36
+ provider: string;
17
37
  }
18
38
 
19
39
  export interface CreateOptions {
@@ -85,6 +105,7 @@ export interface Health {
85
105
  /** True once the mount table was applied (by the boot hook or /configure). */
86
106
  configured: boolean;
87
107
  mounts?: MountResult[];
108
+ network?: NetworkStatus;
88
109
  /** The template's long-running process (CAPSULOD_START), when the template declares one. */
89
110
  service?: ServiceStatus;
90
111
  }
@@ -98,6 +119,35 @@ export interface ServiceStatus {
98
119
  startedAt?: string;
99
120
  }
100
121
 
122
+ /**
123
+ * What the capsule may reach. With `internet: 'deny'` the daemon runs an
124
+ * egress proxy limited to `allow` (HTTP_PROXY/HTTPS_PROXY point at it for
125
+ * every process) and blackholes the default route, so a process that ignores
126
+ * the proxy gets "network unreachable" rather than the internet. Needs
127
+ * CAP_NET_ADMIN in the capsule: Lambda templates built with
128
+ * `--capabilities all`; the Docker provider adds it. Creation fails if the
129
+ * policy cannot be enforced.
130
+ */
131
+ export interface NetworkPolicy {
132
+ /** 'allow' (default): unrestricted. 'deny': nothing leaves except `allow`. */
133
+ internet: 'allow' | 'deny';
134
+ /** Destinations still reachable with 'deny': 'host', '*.domain' (subdomains only) or 'host:port'. */
135
+ allow?: string[];
136
+ }
137
+
138
+ /** The daemon's view of the policy (`health().network`; absent without a policy). */
139
+ export interface NetworkStatus {
140
+ internet: 'allow' | 'deny';
141
+ allow?: string[];
142
+ /** True once the proxy runs and the routes are locked. */
143
+ enforced: boolean;
144
+ /** The egress proxy's address inside the capsule (127.0.0.1:3128). */
145
+ proxy?: string;
146
+ /** Connections refused so far. */
147
+ blocked: number;
148
+ error?: string;
149
+ }
150
+
101
151
  export interface IdlePolicy {
102
152
  /** Suspend after this long without traffic (seconds). */
103
153
  suspendAfterSeconds: number;
@@ -114,6 +164,8 @@ export interface CapsuleSpec {
114
164
  mounts?: Record<string, MountSpec>;
115
165
  /** Opaque per-capsule configuration delivered to the daemon at boot (never secrets). */
116
166
  metadata?: Record<string, string>;
167
+ /** Egress policy, enforced inside the capsule by the daemon (default: unrestricted). */
168
+ network?: NetworkPolicy;
117
169
  /** Idempotency key: creating twice with the same key returns the same capsule. */
118
170
  clientToken?: string;
119
171
  }