@secure-exec/browser 0.0.0-agentos-dylib-base.edaa4a4

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.
Files changed (66) hide show
  1. package/README.md +6 -0
  2. package/dist/child-process-bridge.d.ts +25 -0
  3. package/dist/child-process-bridge.js +50 -0
  4. package/dist/converged-base64.d.ts +2 -0
  5. package/dist/converged-base64.js +41 -0
  6. package/dist/converged-dgram-bridge.d.ts +11 -0
  7. package/dist/converged-dgram-bridge.js +147 -0
  8. package/dist/converged-driver-setup.d.ts +22 -0
  9. package/dist/converged-driver-setup.js +72 -0
  10. package/dist/converged-execution-host-bridge.d.ts +7 -0
  11. package/dist/converged-execution-host-bridge.js +85 -0
  12. package/dist/converged-executor-session.d.ts +60 -0
  13. package/dist/converged-executor-session.js +127 -0
  14. package/dist/converged-fs-bridge.d.ts +42 -0
  15. package/dist/converged-fs-bridge.js +245 -0
  16. package/dist/converged-module-servicer.d.ts +8 -0
  17. package/dist/converged-module-servicer.js +79 -0
  18. package/dist/converged-net-bridge.d.ts +28 -0
  19. package/dist/converged-net-bridge.js +155 -0
  20. package/dist/converged-permissions.d.ts +9 -0
  21. package/dist/converged-permissions.js +46 -0
  22. package/dist/converged-sync-bridge-handler.d.ts +47 -0
  23. package/dist/converged-sync-bridge-handler.js +140 -0
  24. package/dist/converged-sync-bridge-router.d.ts +33 -0
  25. package/dist/converged-sync-bridge-router.js +41 -0
  26. package/dist/driver.d.ts +91 -0
  27. package/dist/driver.js +386 -0
  28. package/dist/encoding.d.ts +4 -0
  29. package/dist/encoding.js +102 -0
  30. package/dist/generated/util-polyfill.d.ts +1 -0
  31. package/dist/generated/util-polyfill.js +2 -0
  32. package/dist/index.d.ts +9 -0
  33. package/dist/index.js +5 -0
  34. package/dist/kernel-backed-filesystem.d.ts +33 -0
  35. package/dist/kernel-backed-filesystem.js +205 -0
  36. package/dist/os-filesystem.d.ts +47 -0
  37. package/dist/os-filesystem.js +409 -0
  38. package/dist/permission-validation.d.ts +15 -0
  39. package/dist/permission-validation.js +62 -0
  40. package/dist/root-filesystem-from-vfs.d.ts +13 -0
  41. package/dist/root-filesystem-from-vfs.js +95 -0
  42. package/dist/runtime-driver.d.ts +66 -0
  43. package/dist/runtime-driver.js +611 -0
  44. package/dist/runtime.d.ts +248 -0
  45. package/dist/runtime.js +2296 -0
  46. package/dist/sidecar-wasm-module.d.ts +62 -0
  47. package/dist/sidecar-wasm-module.js +28 -0
  48. package/dist/sidecar-worker-protocol.d.ts +14 -0
  49. package/dist/sidecar-worker-protocol.js +9 -0
  50. package/dist/sidecar-worker.d.ts +19 -0
  51. package/dist/sidecar-worker.js +63 -0
  52. package/dist/signals.d.ts +13 -0
  53. package/dist/signals.js +89 -0
  54. package/dist/sync-bridge.d.ts +50 -0
  55. package/dist/sync-bridge.js +93 -0
  56. package/dist/wasi-polyfill.d.ts +1 -0
  57. package/dist/wasi-polyfill.js +2154 -0
  58. package/dist/worker-adapter.d.ts +21 -0
  59. package/dist/worker-adapter.js +41 -0
  60. package/dist/worker-protocol.d.ts +104 -0
  61. package/dist/worker-protocol.js +1 -0
  62. package/dist/worker-sidecar-client.d.ts +71 -0
  63. package/dist/worker-sidecar-client.js +152 -0
  64. package/dist/worker.d.ts +1 -0
  65. package/dist/worker.js +2125 -0
  66. package/package.json +111 -0
@@ -0,0 +1,42 @@
1
+ import type { LiveGuestFilesystemStat, LiveRequestPayload, LiveResponsePayload } from "@secure-exec/core";
2
+ import type { VirtualDirEntry, VirtualStat } from "./runtime.js";
3
+ import { SYNC_BRIDGE_KIND_BINARY, SYNC_BRIDGE_KIND_JSON, SYNC_BRIDGE_KIND_NONE, SYNC_BRIDGE_KIND_TEXT } from "./sync-bridge.js";
4
+ /** The wire `guest_filesystem_call` request payload. */
5
+ export type GuestFilesystemRequestPayload = Extract<LiveRequestPayload, {
6
+ type: "guest_filesystem_call";
7
+ }>;
8
+ /** The wire `guest_filesystem_result` fields the response mapper consumes. */
9
+ export type GuestFilesystemResult = Extract<LiveResponsePayload, {
10
+ type: "guest_filesystem_result";
11
+ }>;
12
+ /** The shape `runtime-driver` writes back to the sync-bridge SAB. */
13
+ export type ConvergedSyncResponse = {
14
+ kind: typeof SYNC_BRIDGE_KIND_NONE;
15
+ } | {
16
+ kind: typeof SYNC_BRIDGE_KIND_TEXT;
17
+ value: string;
18
+ } | {
19
+ kind: typeof SYNC_BRIDGE_KIND_BINARY;
20
+ value: Uint8Array;
21
+ } | {
22
+ kind: typeof SYNC_BRIDGE_KIND_JSON;
23
+ value: unknown;
24
+ };
25
+ export declare function isSingleCallFilesystemOperation(operation: string): boolean;
26
+ /**
27
+ * Translate a sync-bridge `fs.*` operation + args into the wire
28
+ * `guest_filesystem_call` request payload. Throws for operations that are not a
29
+ * single-call mapping (e.g. `fs.readDir`, which the handler expands with
30
+ * per-entry `lstat` to recover Dirent types the wire does not carry).
31
+ */
32
+ export declare function convergedFilesystemRequestPayload(operation: string, args: readonly unknown[]): GuestFilesystemRequestPayload;
33
+ /**
34
+ * Translate a wire `guest_filesystem_result` back into the sync-bridge response
35
+ * the guest expects for `operation`. `fs.readDir` is intentionally NOT handled
36
+ * here (it requires multiple wire calls); the handler maps it separately.
37
+ */
38
+ export declare function convergedFilesystemSyncResponse(operation: string, result: GuestFilesystemResult): ConvergedSyncResponse;
39
+ /** Map a wire snake_case stat into the guest-facing camelCase `VirtualStat`. */
40
+ export declare function wireStatToVirtualStat(stat: LiveGuestFilesystemStat): VirtualStat;
41
+ /** Build a `VirtualDirEntry` from a directory child name and its lstat. */
42
+ export declare function wireStatToDirEntry(name: string, stat: LiveGuestFilesystemStat): VirtualDirEntry;
@@ -0,0 +1,245 @@
1
+ // Converged filesystem bridge translation layer.
2
+ //
3
+ // The legacy browser executor serviced guest `fs.*` sync-bridge operations
4
+ // against an in-process TypeScript kernel (`runtime-driver.ts`'s
5
+ // `handleSyncBridgeOperation`). The converged executor instead routes every
6
+ // guest filesystem syscall to the wasm sidecar (`crates/sidecar-browser`) over
7
+ // the wire protocol, so the kernel is the single enforcement point on both
8
+ // native and browser.
9
+ //
10
+ // This module is the pure, backend-agnostic translation between a sync-bridge
11
+ // `fs.*` operation (the guest-facing shape in `worker.ts`) and the wire
12
+ // `guest_filesystem_call` request / `guest_filesystem_result` response. It is
13
+ // unit-tested in Node without a wasm sidecar or a worker; the handler that owns
14
+ // a `BrowserSidecarWasm` instance (and the multi-call `readDir`-with-types
15
+ // expansion) builds on top of it.
16
+ import { decodeBase64, encodeBase64 } from "./converged-base64.js";
17
+ import { SYNC_BRIDGE_KIND_BINARY, SYNC_BRIDGE_KIND_JSON, SYNC_BRIDGE_KIND_NONE, SYNC_BRIDGE_KIND_TEXT, } from "./sync-bridge.js";
18
+ /** Operations serviced by a single wire `guest_filesystem_call` round-trip. */
19
+ const SINGLE_CALL_FS_OPERATIONS = new Set([
20
+ "fs.readFile",
21
+ "fs.writeFile",
22
+ "fs.readFileBinary",
23
+ "fs.writeFileBinary",
24
+ "fs.createDir",
25
+ "fs.mkdir",
26
+ "fs.rmdir",
27
+ "fs.exists",
28
+ "fs.stat",
29
+ "fs.lstat",
30
+ "fs.unlink",
31
+ "fs.rename",
32
+ "fs.realpath",
33
+ "fs.readlink",
34
+ "fs.symlink",
35
+ "fs.link",
36
+ "fs.chmod",
37
+ "fs.truncate",
38
+ ]);
39
+ export function isSingleCallFilesystemOperation(operation) {
40
+ return SINGLE_CALL_FS_OPERATIONS.has(operation);
41
+ }
42
+ /**
43
+ * Translate a sync-bridge `fs.*` operation + args into the wire
44
+ * `guest_filesystem_call` request payload. Throws for operations that are not a
45
+ * single-call mapping (e.g. `fs.readDir`, which the handler expands with
46
+ * per-entry `lstat` to recover Dirent types the wire does not carry).
47
+ */
48
+ export function convergedFilesystemRequestPayload(operation, args) {
49
+ const path = String(args[0]);
50
+ switch (operation) {
51
+ case "fs.readFile":
52
+ case "fs.readFileBinary":
53
+ return { type: "guest_filesystem_call", operation: "read_file", path };
54
+ case "fs.writeFile":
55
+ return {
56
+ type: "guest_filesystem_call",
57
+ operation: "write_file",
58
+ path,
59
+ content: String(args[1] ?? ""),
60
+ encoding: "utf8",
61
+ };
62
+ case "fs.writeFileBinary":
63
+ return {
64
+ type: "guest_filesystem_call",
65
+ operation: "write_file",
66
+ path,
67
+ content: encodeBase64(toUint8Array(args[1])),
68
+ encoding: "base64",
69
+ };
70
+ case "fs.createDir":
71
+ return { type: "guest_filesystem_call", operation: "create_dir", path };
72
+ case "fs.mkdir":
73
+ return {
74
+ type: "guest_filesystem_call",
75
+ operation: "mkdir",
76
+ path,
77
+ recursive: true,
78
+ };
79
+ case "fs.rmdir":
80
+ return { type: "guest_filesystem_call", operation: "remove_dir", path };
81
+ case "fs.exists":
82
+ return { type: "guest_filesystem_call", operation: "exists", path };
83
+ case "fs.stat":
84
+ return { type: "guest_filesystem_call", operation: "stat", path };
85
+ case "fs.lstat":
86
+ return { type: "guest_filesystem_call", operation: "lstat", path };
87
+ case "fs.unlink":
88
+ return { type: "guest_filesystem_call", operation: "remove_file", path };
89
+ case "fs.rename":
90
+ return {
91
+ type: "guest_filesystem_call",
92
+ operation: "rename",
93
+ path,
94
+ destination_path: String(args[1]),
95
+ };
96
+ case "fs.realpath":
97
+ return { type: "guest_filesystem_call", operation: "realpath", path };
98
+ case "fs.readlink":
99
+ return { type: "guest_filesystem_call", operation: "read_link", path };
100
+ case "fs.symlink":
101
+ // Legacy arg order is (target, linkPath); the wire `path` is the link.
102
+ return {
103
+ type: "guest_filesystem_call",
104
+ operation: "symlink",
105
+ path: String(args[1]),
106
+ target: String(args[0]),
107
+ };
108
+ case "fs.link":
109
+ return {
110
+ type: "guest_filesystem_call",
111
+ operation: "link",
112
+ path,
113
+ destination_path: String(args[1]),
114
+ };
115
+ case "fs.chmod":
116
+ return {
117
+ type: "guest_filesystem_call",
118
+ operation: "chmod",
119
+ path,
120
+ mode: Number(args[1]),
121
+ };
122
+ case "fs.truncate":
123
+ return {
124
+ type: "guest_filesystem_call",
125
+ operation: "truncate",
126
+ path,
127
+ len: Number(args[1]),
128
+ };
129
+ default:
130
+ throw new Error(`converged filesystem bridge has no single-call mapping for ${operation}`);
131
+ }
132
+ }
133
+ /**
134
+ * Translate a wire `guest_filesystem_result` back into the sync-bridge response
135
+ * the guest expects for `operation`. `fs.readDir` is intentionally NOT handled
136
+ * here (it requires multiple wire calls); the handler maps it separately.
137
+ */
138
+ export function convergedFilesystemSyncResponse(operation, result) {
139
+ switch (operation) {
140
+ case "fs.readFile":
141
+ return {
142
+ kind: SYNC_BRIDGE_KIND_TEXT,
143
+ value: decodeTextContent(result),
144
+ };
145
+ case "fs.readFileBinary":
146
+ return {
147
+ kind: SYNC_BRIDGE_KIND_BINARY,
148
+ value: decodeBinaryContent(result),
149
+ };
150
+ case "fs.realpath":
151
+ case "fs.readlink":
152
+ return {
153
+ kind: SYNC_BRIDGE_KIND_TEXT,
154
+ value: result.target ?? "",
155
+ };
156
+ case "fs.exists":
157
+ return {
158
+ kind: SYNC_BRIDGE_KIND_JSON,
159
+ value: result.exists ?? false,
160
+ };
161
+ case "fs.stat":
162
+ case "fs.lstat":
163
+ return {
164
+ kind: SYNC_BRIDGE_KIND_JSON,
165
+ value: wireStatToVirtualStat(requireStat(result)),
166
+ };
167
+ case "fs.writeFile":
168
+ case "fs.writeFileBinary":
169
+ case "fs.createDir":
170
+ case "fs.mkdir":
171
+ case "fs.rmdir":
172
+ case "fs.unlink":
173
+ case "fs.rename":
174
+ case "fs.symlink":
175
+ case "fs.link":
176
+ case "fs.chmod":
177
+ case "fs.truncate":
178
+ return { kind: SYNC_BRIDGE_KIND_NONE };
179
+ default:
180
+ throw new Error(`converged filesystem bridge has no response mapping for ${operation}`);
181
+ }
182
+ }
183
+ /** Map a wire snake_case stat into the guest-facing camelCase `VirtualStat`. */
184
+ export function wireStatToVirtualStat(stat) {
185
+ return {
186
+ mode: stat.mode,
187
+ size: stat.size,
188
+ blocks: stat.blocks,
189
+ dev: stat.dev,
190
+ rdev: stat.rdev,
191
+ isDirectory: stat.is_directory,
192
+ isSymbolicLink: stat.is_symbolic_link,
193
+ atimeMs: stat.atime_ms,
194
+ mtimeMs: stat.mtime_ms,
195
+ ctimeMs: stat.ctime_ms,
196
+ birthtimeMs: stat.birthtime_ms,
197
+ ino: stat.ino,
198
+ nlink: stat.nlink,
199
+ uid: stat.uid,
200
+ gid: stat.gid,
201
+ };
202
+ }
203
+ /** Build a `VirtualDirEntry` from a directory child name and its lstat. */
204
+ export function wireStatToDirEntry(name, stat) {
205
+ return {
206
+ name,
207
+ isDirectory: stat.is_directory,
208
+ isSymbolicLink: stat.is_symbolic_link,
209
+ };
210
+ }
211
+ function requireStat(result) {
212
+ if (!result.stat) {
213
+ throw new Error(`guest_filesystem_result for ${result.operation} is missing stat`);
214
+ }
215
+ return result.stat;
216
+ }
217
+ function decodeTextContent(result) {
218
+ const content = result.content ?? "";
219
+ if (result.encoding === "base64") {
220
+ return new TextDecoder().decode(decodeBase64(content));
221
+ }
222
+ return content;
223
+ }
224
+ function decodeBinaryContent(result) {
225
+ const content = result.content ?? "";
226
+ if (result.encoding === "base64") {
227
+ return decodeBase64(content);
228
+ }
229
+ return new TextEncoder().encode(content);
230
+ }
231
+ function toUint8Array(value) {
232
+ if (value instanceof Uint8Array) {
233
+ return value;
234
+ }
235
+ if (value instanceof ArrayBuffer) {
236
+ return new Uint8Array(value);
237
+ }
238
+ if (ArrayBuffer.isView(value)) {
239
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
240
+ }
241
+ if (Array.isArray(value)) {
242
+ return Uint8Array.from(value);
243
+ }
244
+ throw new Error("converged filesystem bridge expected binary content");
245
+ }
@@ -0,0 +1,8 @@
1
+ import type { ConvergedSyncResponse } from "./converged-fs-bridge.js";
2
+ import type { VirtualFileSystem } from "./runtime.js";
3
+ export declare class ConvergedModuleServicer {
4
+ private readonly filesystem;
5
+ constructor(filesystem: VirtualFileSystem);
6
+ handles(operation: string): boolean;
7
+ handle(operation: string, args: readonly unknown[]): Promise<ConvergedSyncResponse>;
8
+ }
@@ -0,0 +1,79 @@
1
+ // Converged module-resolution servicer.
2
+ //
3
+ // Reuses the shared naive-Node resolver (`resolveModule`/`loadFile`/
4
+ // `moduleFormat` from runtime.ts) unchanged, backed by a kernel-backed
5
+ // filesystem so resolution reads the kernel's exact view (mounts, symlinks,
6
+ // exports/conditions) on the converged path — satisfying convergence item K
7
+ // (one module resolver) without forking it. Async because the resolver walks
8
+ // the filesystem; the router awaits it.
9
+ import { loadFile, moduleFormat, resolveModule } from "./runtime.js";
10
+ import { SYNC_BRIDGE_KIND_JSON, SYNC_BRIDGE_KIND_NONE, SYNC_BRIDGE_KIND_TEXT, } from "./sync-bridge.js";
11
+ const MODULE_OPERATIONS = new Set([
12
+ "module.resolve",
13
+ "module.loadFile",
14
+ "module.format",
15
+ "module.batchResolve",
16
+ ]);
17
+ export class ConvergedModuleServicer {
18
+ filesystem;
19
+ constructor(filesystem) {
20
+ this.filesystem = filesystem;
21
+ }
22
+ handles(operation) {
23
+ return MODULE_OPERATIONS.has(operation);
24
+ }
25
+ async handle(operation, args) {
26
+ switch (operation) {
27
+ case "module.resolve": {
28
+ const mode = args[2] === "import" || args[2] === "require" ? args[2] : "require";
29
+ const resolved = await resolveModule(String(args[0]), String(args[1]), this.filesystem, mode);
30
+ return resolved === null
31
+ ? { kind: SYNC_BRIDGE_KIND_NONE }
32
+ : { kind: SYNC_BRIDGE_KIND_TEXT, value: resolved };
33
+ }
34
+ case "module.loadFile": {
35
+ const source = await loadFile(String(args[0]), this.filesystem);
36
+ return source === null
37
+ ? { kind: SYNC_BRIDGE_KIND_NONE }
38
+ : { kind: SYNC_BRIDGE_KIND_TEXT, value: source };
39
+ }
40
+ case "module.format": {
41
+ const format = await moduleFormat(String(args[0]), this.filesystem);
42
+ return format === null
43
+ ? { kind: SYNC_BRIDGE_KIND_NONE }
44
+ : { kind: SYNC_BRIDGE_KIND_TEXT, value: format };
45
+ }
46
+ case "module.batchResolve": {
47
+ const requests = parseModuleBatchRequests(args[0]);
48
+ const results = [];
49
+ for (const [specifier, referrer] of requests) {
50
+ const resolved = await resolveModule(specifier, referrer, this.filesystem, "import");
51
+ if (resolved === null) {
52
+ results.push(null);
53
+ continue;
54
+ }
55
+ const source = await loadFile(resolved, this.filesystem);
56
+ results.push(source === null ? null : { resolved, source });
57
+ }
58
+ return { kind: SYNC_BRIDGE_KIND_JSON, value: results };
59
+ }
60
+ default:
61
+ throw new Error(`converged module servicer does not handle ${operation}`);
62
+ }
63
+ }
64
+ }
65
+ function parseModuleBatchRequests(value) {
66
+ const parsed = typeof value === "string" ? JSON.parse(value) : value;
67
+ if (!Array.isArray(parsed)) {
68
+ throw new Error("module.batchResolve requests must be an array");
69
+ }
70
+ return parsed.map((entry) => {
71
+ if (!Array.isArray(entry) ||
72
+ entry.length < 2 ||
73
+ typeof entry[0] !== "string" ||
74
+ typeof entry[1] !== "string") {
75
+ throw new Error("module.batchResolve requests must be [specifier, referrer] pairs");
76
+ }
77
+ return [entry[0], entry[1]];
78
+ });
79
+ }
@@ -0,0 +1,28 @@
1
+ import type { LiveRequestPayload, LiveResponsePayload } from "@secure-exec/core";
2
+ import { SYNC_BRIDGE_KIND_JSON } from "./sync-bridge.js";
3
+ export type GuestKernelCallRequestPayload = Extract<LiveRequestPayload, {
4
+ type: "guest_kernel_call";
5
+ }>;
6
+ export type GuestKernelResult = Extract<LiveResponsePayload, {
7
+ type: "guest_kernel_result";
8
+ }>;
9
+ /** Guest network/DNS sync-bridge operations serviced by the wasm sidecar. */
10
+ export declare const CONVERGED_NET_BRIDGE_OPERATIONS: readonly ["net.connect", "net.listen", "net.accept", "net.read", "net.write", "net.poll", "net.shutdown", "net.close", "net.udp_bind", "net.send_to", "net.recv_from", "dns.lookup"];
11
+ export declare function isConvergedNetBridgeOperation(operation: string): boolean;
12
+ /**
13
+ * Translate a guest `net.*` / `dns.*` sync-bridge call into a
14
+ * `guest_kernel_call` request payload. `args[0]` is the structured request
15
+ * object (the guest net module passes a typed options object); binary `data`
16
+ * fields are base64-encoded into the JSON the kernel dispatcher expects.
17
+ */
18
+ export declare function convergedNetRequestPayload(operation: string, args: readonly unknown[], executionId: string): GuestKernelCallRequestPayload;
19
+ /**
20
+ * Decode a `guest_kernel_result` into the sync-bridge response the guest net
21
+ * module consumes. The kernel dispatcher already returns JSON; binary read
22
+ * payloads stay base64 strings (the guest module decodes them), so this is a
23
+ * JSON passthrough.
24
+ */
25
+ export declare function convergedNetSyncResponse(result: GuestKernelResult): {
26
+ kind: typeof SYNC_BRIDGE_KIND_JSON;
27
+ value: unknown;
28
+ };
@@ -0,0 +1,155 @@
1
+ // Converged networking bridge translation layer.
2
+ //
3
+ // Unlike `fs.*`, legacy guest networking never went through the SAB sync-bridge
4
+ // (it used an async network adapter), so there is no legacy op to mirror. The
5
+ // converged executor introduces synchronous guest `net.*` / `dns.*` sync-bridge
6
+ // operations that route to the wasm sidecar's generic guest-kernel-call wire
7
+ // payload (`guest_kernel_call` -> `secure_exec_sidecar_core::guest_net`), which
8
+ // drives the kernel socket table (the single network-policy enforcement point,
9
+ // S1) over loopback.
10
+ //
11
+ // This module is the pure translation between a guest net/dns sync-bridge call
12
+ // and the `guest_kernel_call` request / `guest_kernel_result` response. The
13
+ // wire `operation` string is identical to the sync-bridge op string, and the
14
+ // `payload` is the JSON request body the Rust dispatcher decodes. Binary socket
15
+ // payloads are base64-encoded (the wire `payload` is opaque JSON). Unit-tested
16
+ // in Node without a wasm sidecar.
17
+ import { encodeBase64 } from "./converged-base64.js";
18
+ import { SYNC_BRIDGE_KIND_JSON } from "./sync-bridge.js";
19
+ /** Guest network/DNS sync-bridge operations serviced by the wasm sidecar. */
20
+ export const CONVERGED_NET_BRIDGE_OPERATIONS = [
21
+ "net.connect",
22
+ "net.listen",
23
+ "net.accept",
24
+ "net.read",
25
+ "net.write",
26
+ "net.poll",
27
+ "net.shutdown",
28
+ "net.close",
29
+ "net.udp_bind",
30
+ "net.send_to",
31
+ "net.recv_from",
32
+ "dns.lookup",
33
+ ];
34
+ const CONVERGED_NET_BRIDGE_OPERATION_SET = new Set(CONVERGED_NET_BRIDGE_OPERATIONS);
35
+ export function isConvergedNetBridgeOperation(operation) {
36
+ return CONVERGED_NET_BRIDGE_OPERATION_SET.has(operation);
37
+ }
38
+ /**
39
+ * Translate a guest `net.*` / `dns.*` sync-bridge call into a
40
+ * `guest_kernel_call` request payload. `args[0]` is the structured request
41
+ * object (the guest net module passes a typed options object); binary `data`
42
+ * fields are base64-encoded into the JSON the kernel dispatcher expects.
43
+ */
44
+ export function convergedNetRequestPayload(operation, args, executionId) {
45
+ if (!isConvergedNetBridgeOperation(operation)) {
46
+ throw new Error(`converged net bridge has no mapping for ${operation}`);
47
+ }
48
+ const options = (args[0] ?? {});
49
+ const request = buildKernelRequest(operation, options);
50
+ return {
51
+ type: "guest_kernel_call",
52
+ execution_id: executionId,
53
+ operation,
54
+ payload: encodeJsonBytes(request),
55
+ };
56
+ }
57
+ /**
58
+ * Decode a `guest_kernel_result` into the sync-bridge response the guest net
59
+ * module consumes. The kernel dispatcher already returns JSON; binary read
60
+ * payloads stay base64 strings (the guest module decodes them), so this is a
61
+ * JSON passthrough.
62
+ */
63
+ export function convergedNetSyncResponse(result) {
64
+ return {
65
+ kind: SYNC_BRIDGE_KIND_JSON,
66
+ value: decodeJsonBytes(result.payload),
67
+ };
68
+ }
69
+ function buildKernelRequest(operation, options) {
70
+ switch (operation) {
71
+ case "net.connect":
72
+ case "net.listen":
73
+ case "net.udp_bind":
74
+ return stripUndefined({
75
+ host: options.host,
76
+ port: options.port,
77
+ backlog: options.backlog,
78
+ });
79
+ case "net.accept":
80
+ case "net.close":
81
+ return { socketId: requireSocketId(options) };
82
+ case "net.read":
83
+ case "net.recv_from":
84
+ return stripUndefined({
85
+ socketId: requireSocketId(options),
86
+ maxBytes: options.maxBytes,
87
+ });
88
+ case "net.write":
89
+ return {
90
+ socketId: requireSocketId(options),
91
+ data: encodeBase64(requireData(options)),
92
+ };
93
+ case "net.send_to":
94
+ return stripUndefined({
95
+ socketId: requireSocketId(options),
96
+ host: options.host,
97
+ port: options.port,
98
+ data: encodeBase64(requireData(options)),
99
+ });
100
+ case "net.poll":
101
+ return stripUndefined({
102
+ socketId: requireSocketId(options),
103
+ events: options.events,
104
+ timeoutMs: options.timeoutMs,
105
+ });
106
+ case "net.shutdown":
107
+ return {
108
+ socketId: requireSocketId(options),
109
+ how: options.how ?? "both",
110
+ };
111
+ case "dns.lookup":
112
+ return { hostname: String(options.hostname ?? "") };
113
+ default:
114
+ throw new Error(`converged net bridge has no mapping for ${operation}`);
115
+ }
116
+ }
117
+ function requireSocketId(options) {
118
+ const socketId = options.socketId;
119
+ if (typeof socketId !== "number") {
120
+ throw new Error("converged net bridge call requires numeric socketId");
121
+ }
122
+ return socketId;
123
+ }
124
+ function requireData(options) {
125
+ const data = options.data;
126
+ if (data instanceof Uint8Array) {
127
+ return data;
128
+ }
129
+ if (data instanceof ArrayBuffer) {
130
+ return new Uint8Array(data);
131
+ }
132
+ if (ArrayBuffer.isView(data)) {
133
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
134
+ }
135
+ throw new Error("converged net bridge write/send requires binary data");
136
+ }
137
+ function stripUndefined(record) {
138
+ const result = {};
139
+ for (const [key, value] of Object.entries(record)) {
140
+ if (value !== undefined) {
141
+ result[key] = value;
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+ function encodeJsonBytes(value) {
147
+ const bytes = new TextEncoder().encode(JSON.stringify(value));
148
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
149
+ }
150
+ function decodeJsonBytes(payload) {
151
+ if (payload.byteLength === 0) {
152
+ return null;
153
+ }
154
+ return JSON.parse(new TextDecoder().decode(new Uint8Array(payload)));
155
+ }
@@ -0,0 +1,9 @@
1
+ import type { PermissionsPolicy } from "@secure-exec/core/vm-config";
2
+ export interface ConvergedPermissionDenials {
3
+ denyFsRead?: boolean;
4
+ denyChildProcess?: boolean;
5
+ denyNetwork?: boolean;
6
+ denyNetworkPort?: number;
7
+ }
8
+ /** Build a kernel PermissionsPolicy from the harness's declarative denials. */
9
+ export declare function convergedPermissionsPolicy(denials?: ConvergedPermissionDenials): PermissionsPolicy;
@@ -0,0 +1,46 @@
1
+ // Declarative kernel permission policies for the converged conformance harness.
2
+ //
3
+ // The legacy browser harness expressed permission tests as TS callbacks on the
4
+ // in-process kernel (e.g. deny fs reads, deny a network port). The converged
5
+ // path enforces permissions in the wasm kernel via a declarative
6
+ // `PermissionsPolicy` on `CreateVmConfig`, so those test intents are translated
7
+ // here into rule sets the kernel evaluates. Operation names match the kernel's
8
+ // (`crates/kernel/src/permissions.rs`).
9
+ // Kernel fs read-family operation names (mirrors FilesystemOperation::as_str).
10
+ const FS_READ_OPERATIONS = ["read", "readdir"];
11
+ /** Build a kernel PermissionsPolicy from the harness's declarative denials. */
12
+ export function convergedPermissionsPolicy(denials = {}) {
13
+ const policy = {
14
+ fs: "allow",
15
+ network: "allow",
16
+ childProcess: "allow",
17
+ process: "allow",
18
+ env: "allow",
19
+ tool: "allow",
20
+ };
21
+ if (denials.denyFsRead) {
22
+ policy.fs = {
23
+ default: "allow",
24
+ rules: [{ mode: "deny", operations: FS_READ_OPERATIONS, paths: ["**"] }],
25
+ };
26
+ }
27
+ if (denials.denyChildProcess) {
28
+ policy.childProcess = "deny";
29
+ }
30
+ if (denials.denyNetwork) {
31
+ policy.network = "deny";
32
+ }
33
+ if (denials.denyNetworkPort !== undefined) {
34
+ policy.network = {
35
+ default: "allow",
36
+ rules: [
37
+ {
38
+ mode: "deny",
39
+ operations: ["listen", "http", "fetch"],
40
+ patterns: [`tcp://*:${denials.denyNetworkPort}`],
41
+ },
42
+ ],
43
+ };
44
+ }
45
+ return policy;
46
+ }
@@ -0,0 +1,47 @@
1
+ import type { LiveOwnershipScope } from "@secure-exec/core/ownership";
2
+ import { type ProtocolFramePayloadCodec } from "@secure-exec/core/protocol-frames";
3
+ import type { LiveRequestPayload } from "@secure-exec/core/request-payloads";
4
+ import type { LiveResponsePayload } from "@secure-exec/core/response-payloads";
5
+ import { type ConvergedSyncResponse } from "./converged-fs-bridge.js";
6
+ /** Synchronous wasm sidecar frame dispatcher (`BrowserSidecarWasm.pushFrame`). */
7
+ export type ConvergedPushFrame = (frame: Uint8Array) => Uint8Array;
8
+ /**
9
+ * Synchronous request transport seam between the handler and the wasm sidecar.
10
+ * Splitting this out keeps the handler's translation logic unit-testable without
11
+ * the directional wire codec (the host can only encode request frames, and the
12
+ * JSON codec does not round-trip binary `data` payloads).
13
+ */
14
+ export interface ConvergedSidecarRequestTransport {
15
+ sendRequest(payload: LiveRequestPayload): LiveResponsePayload;
16
+ }
17
+ /**
18
+ * Production transport: encodes a request frame, calls the synchronous wasm
19
+ * `pushFrame`, and decodes the response frame.
20
+ */
21
+ export declare class PushFrameSidecarTransport implements ConvergedSidecarRequestTransport {
22
+ private readonly frames;
23
+ private readonly pushFrame;
24
+ private readonly ownership;
25
+ private readonly codec;
26
+ constructor(options: {
27
+ pushFrame: ConvergedPushFrame;
28
+ ownership: LiveOwnershipScope;
29
+ codec?: ProtocolFramePayloadCodec;
30
+ });
31
+ sendRequest(payload: LiveRequestPayload): LiveResponsePayload;
32
+ }
33
+ export interface ConvergedSyncBridgeHandlerOptions {
34
+ transport: ConvergedSidecarRequestTransport;
35
+ executionId: string;
36
+ }
37
+ export declare class ConvergedSyncBridgeHandler {
38
+ private readonly transport;
39
+ private readonly executionId;
40
+ constructor(options: ConvergedSyncBridgeHandlerOptions);
41
+ /** True if this handler services `operation` against the wasm sidecar. */
42
+ handles(operation: string): boolean;
43
+ handle(operation: string, args: readonly unknown[]): ConvergedSyncResponse;
44
+ private readDir;
45
+ private callFilesystem;
46
+ private callKernel;
47
+ }