knitting 0.1.56 → 0.1.61

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 (101) hide show
  1. package/BROWSER.md +134 -0
  2. package/README.md +347 -46
  3. package/knitting.browser.d.ts +6 -0
  4. package/knitting.browser.js +1 -0
  5. package/knitting.d.ts +3 -2
  6. package/knitting.js +3 -3
  7. package/map.md +21 -9
  8. package/package.json +17 -2
  9. package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
  10. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  11. package/prebuilds/darwin-arm64-node-127/knitting_shm.node +0 -0
  12. package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
  13. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  14. package/prebuilds/darwin-arm64-node-137/knitting_shm.node +0 -0
  15. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  16. package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
  17. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  18. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  19. package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
  20. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  21. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  22. package/scripts/build-compiled-worker.ts +276 -0
  23. package/scripts/compiled-worker/porffor.d.ts +5 -0
  24. package/scripts/compiled-worker/runtime.ts +529 -0
  25. package/scripts/compiled-worker/task-shim.ts +32 -0
  26. package/src/api.d.ts +24 -1
  27. package/src/api.js +311 -27
  28. package/src/common/module-url.d.ts +1 -0
  29. package/src/common/module-url.js +30 -2
  30. package/src/common/node-compat.d.ts +2 -0
  31. package/src/common/path-canonical.js +20 -6
  32. package/src/common/runtime.d.ts +3 -1
  33. package/src/common/runtime.js +25 -4
  34. package/src/common/task-source.d.ts +2 -0
  35. package/src/common/task-source.js +17 -1
  36. package/src/common/worker-runtime.js +16 -7
  37. package/src/connections/buffer-reference-native.js +25 -1
  38. package/src/connections/buffer-reference.d.ts +23 -2
  39. package/src/connections/buffer-reference.js +143 -29
  40. package/src/connections/bun.d.ts +1 -1
  41. package/src/connections/bun.js +25 -14
  42. package/src/connections/deno.d.ts +3 -1
  43. package/src/connections/deno.js +35 -14
  44. package/src/connections/external-array-buffer.d.ts +2 -0
  45. package/src/connections/external-array-buffer.js +33 -0
  46. package/src/connections/node-addons.d.ts +11 -0
  47. package/src/connections/node-addons.js +49 -1
  48. package/src/connections/node-ffi-api.d.ts +19 -0
  49. package/src/connections/node-ffi-api.js +28 -0
  50. package/src/connections/node-ffi.d.ts +27 -0
  51. package/src/connections/node-ffi.js +262 -0
  52. package/src/connections/node.d.ts +1 -1
  53. package/src/connections/node.js +20 -20
  54. package/src/connections/package-assets.js +25 -15
  55. package/src/connections/posix.d.ts +42 -0
  56. package/src/connections/posix.js +101 -0
  57. package/src/connections/process-shared-buffer.d.ts +1 -0
  58. package/src/connections/process-shared-buffer.js +11 -45
  59. package/src/connections/types.d.ts +4 -0
  60. package/src/connections/windows.d.ts +19 -10
  61. package/src/connections/windows.js +87 -66
  62. package/src/error.d.ts +7 -6
  63. package/src/error.js +8 -7
  64. package/src/memory/lock.d.ts +112 -77
  65. package/src/memory/lock.js +314 -87
  66. package/src/memory/payloadCodec.d.ts +11 -2
  67. package/src/memory/payloadCodec.js +129 -37
  68. package/src/memory/regionRegistry.d.ts +1 -3
  69. package/src/memory/regionRegistry.js +16 -14
  70. package/src/permission/compatibility.d.ts +28 -0
  71. package/src/permission/compatibility.js +239 -0
  72. package/src/permission/index.d.ts +2 -0
  73. package/src/permission/index.js +1 -0
  74. package/src/permission/protocol.d.ts +1 -0
  75. package/src/permission/protocol.js +59 -15
  76. package/src/runtime/compiled-artifact.d.ts +21 -0
  77. package/src/runtime/compiled-artifact.js +260 -0
  78. package/src/runtime/compiled-builder.d.ts +8 -0
  79. package/src/runtime/compiled-builder.js +42 -0
  80. package/src/runtime/compiled-worker.d.ts +27 -0
  81. package/src/runtime/compiled-worker.js +562 -0
  82. package/src/runtime/dispatcher.js +9 -0
  83. package/src/runtime/inline-executor.js +27 -15
  84. package/src/runtime/pool.d.ts +121 -5
  85. package/src/runtime/pool.js +263 -49
  86. package/src/runtime/process-worker.d.ts +3 -13
  87. package/src/runtime/process-worker.js +97 -101
  88. package/src/runtime/tx-queue.d.ts +18 -2
  89. package/src/runtime/tx-queue.js +56 -16
  90. package/src/runtime/worker-common.d.ts +13 -0
  91. package/src/runtime/worker-common.js +102 -0
  92. package/src/types.d.ts +89 -9
  93. package/src/worker/composable-runners.js +3 -2
  94. package/src/worker/loop.d.ts +1 -0
  95. package/src/worker/loop.js +52 -17
  96. package/src/worker/rx-queue.d.ts +11 -1
  97. package/src/worker/rx-queue.js +4 -1
  98. package/src/worker/task-loader.d.ts +5 -4
  99. package/src/worker/task-loader.js +13 -7
  100. package/src/worker/timers.d.ts +8 -1
  101. package/src/worker/timers.js +28 -23
@@ -0,0 +1,239 @@
1
+ const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
2
+ const hasExplicit = (value, key) => value !== undefined && hasOwn(value, key);
3
+ const hasNonEmptyArray = (value, key) => Array.isArray(value?.[key]) && value[key].length > 0;
4
+ const explicitProtocolObject = (permission) => permission != null && typeof permission === "object"
5
+ ? permission
6
+ : undefined;
7
+ const explicitNestedObject = (input, key) => {
8
+ const value = input?.[key];
9
+ return value != null && typeof value === "object"
10
+ ? value
11
+ : undefined;
12
+ };
13
+ const targetLabel = (target) => {
14
+ if (target.runtime !== "node") {
15
+ return `${target.runtime[0].toUpperCase()}${target.runtime.slice(1)}`;
16
+ }
17
+ return target.nodeMajor === undefined
18
+ ? "Node (version unknown)"
19
+ : `Node ${target.nodeMajor}`;
20
+ };
21
+ const nodeTargetUsesFfiTransport = (target) => target.runtime === "node" &&
22
+ target.nodeMajor !== undefined &&
23
+ target.nodeMajor >= 26 &&
24
+ target.nodeMajor % 2 === 0;
25
+ const permissionCheck = (permission, level, reason) => ({
26
+ permission,
27
+ level,
28
+ reason,
29
+ });
30
+ const classifyRestriction = ({ target, permission, scoped, processNativeTransport = false, }) => {
31
+ const label = targetLabel(target);
32
+ const exact = (reason) => permissionCheck(permission, "exact", reason);
33
+ const coarse = (reason) => permissionCheck(permission, "coarse", reason);
34
+ const unsupported = (reason) => permissionCheck(permission, "unsupported", reason);
35
+ if (target.runtime === "bun") {
36
+ return unsupported("Bun process workers do not expose a matching runtime permission control.");
37
+ }
38
+ if (target.runtime === "deno") {
39
+ if (permission === "workers") {
40
+ return unsupported("Knitting does not currently configure a Deno worker-spawn restriction.");
41
+ }
42
+ if (permission === "wasi") {
43
+ return unsupported("Deno does not expose a matching Knitting WASI process permission.");
44
+ }
45
+ if (processNativeTransport &&
46
+ (permission === "ffi" || permission === "denyFfi")) {
47
+ return unsupported("Deno process workers require --allow-ffi for Knitting's shared-memory transport.");
48
+ }
49
+ return exact("Deno can represent this restriction with scoped runtime flags.");
50
+ }
51
+ if (permission === "read" || permission === "write") {
52
+ return exact(`${label} supports scoped filesystem allow rules.`);
53
+ }
54
+ if (permission === "run") {
55
+ return scoped
56
+ ? coarse(`${label} can only allow or deny all child processes, not an executable allow-list.`)
57
+ : exact(`${label} can deny all child processes.`);
58
+ }
59
+ if (permission === "workers") {
60
+ return exact(`${label} can deny worker creation.`);
61
+ }
62
+ if (permission === "wasi") {
63
+ return exact(`${label} can deny WASI.`);
64
+ }
65
+ if (permission === "net") {
66
+ if (target.nodeMajor === undefined) {
67
+ return unsupported("The Node version is unknown, so Knitting cannot prove that network denial is available.");
68
+ }
69
+ if (target.nodeMajor < 25) {
70
+ return unsupported(`${label} has no network permission control.`);
71
+ }
72
+ return scoped
73
+ ? coarse(`${label} --allow-net is all-or-nothing and cannot enforce a host allow-list.`)
74
+ : exact(`${label} denies network access when --allow-net is omitted.`);
75
+ }
76
+ if (permission === "node.allowAddons") {
77
+ if (nodeTargetUsesFfiTransport(target)) {
78
+ return exact(`${label} uses node:ffi rather than addons for Knitting's process transport.`);
79
+ }
80
+ return unsupported(`${label} process workers require addon permission for Knitting's shared-memory transport.`);
81
+ }
82
+ if (permission === "node.allowFfi") {
83
+ if (target.nodeMajor !== undefined &&
84
+ !nodeTargetUsesFfiTransport(target)) {
85
+ return exact(`${label} does not use node:ffi for Knitting's process transport.`);
86
+ }
87
+ return unsupported(`${label} process workers require node:ffi permission for Knitting's shared-memory transport.`);
88
+ }
89
+ if (processNativeTransport &&
90
+ (permission === "ffi" || permission === "denyFfi")) {
91
+ return unsupported(`${label} process workers require native-code permission for Knitting's shared-memory transport.`);
92
+ }
93
+ const unsupportedReasons = {
94
+ denyRead: `${label} has no filesystem deny-list control.`,
95
+ denyWrite: `${label} has no filesystem deny-list control.`,
96
+ denyNet: `${label} has no network deny-list control.`,
97
+ allowImport: `${label} has no import-host allow-list control.`,
98
+ env: `${label} has no environment-variable permission control.`,
99
+ denyEnv: `${label} has no environment-variable deny-list control.`,
100
+ denyRun: `${label} has no child-process deny-list control.`,
101
+ sys: `${label} has no system-information permission control.`,
102
+ denySys: `${label} has no system-information deny-list control.`,
103
+ };
104
+ return unsupported(unsupportedReasons[permission] ??
105
+ `${label} cannot represent this restriction.`);
106
+ };
107
+ export const classifyProcessPermissionCompatibility = ({ permission, resolved, target, }) => {
108
+ if (resolved?.enabled !== true ||
109
+ resolved.unsafe === true ||
110
+ permission === "unsafe" ||
111
+ permission === "off") {
112
+ return { target, checks: [] };
113
+ }
114
+ const input = explicitProtocolObject(permission);
115
+ const envInput = explicitNestedObject(input, "env");
116
+ const nodeInput = explicitNestedObject(input, "node");
117
+ const denoInput = explicitNestedObject(input, "deno");
118
+ const checks = [];
119
+ const add = (permissionName, explicit, options = {}) => {
120
+ checks.push({
121
+ ...classifyRestriction({
122
+ target,
123
+ permission: permissionName,
124
+ ...options,
125
+ }),
126
+ explicit,
127
+ });
128
+ };
129
+ if (!resolved.readAll) {
130
+ add("read", hasExplicit(input, "read"), {
131
+ scoped: resolved.read.length > 0,
132
+ });
133
+ }
134
+ if (!resolved.writeAll) {
135
+ add("write", hasExplicit(input, "write"), {
136
+ scoped: resolved.write.length > 0,
137
+ });
138
+ }
139
+ if (resolved.denyRead.length > 0) {
140
+ add("denyRead", hasNonEmptyArray(input, "denyRead"));
141
+ }
142
+ if (resolved.denyWrite.length > 0) {
143
+ add("denyWrite", hasNonEmptyArray(input, "denyWrite"));
144
+ }
145
+ if (!resolved.netAll) {
146
+ add("net", hasExplicit(input, "net"), {
147
+ scoped: resolved.net.length > 0,
148
+ });
149
+ }
150
+ if (resolved.denyNet.length > 0) {
151
+ add("denyNet", hasNonEmptyArray(input, "denyNet"));
152
+ }
153
+ if (!resolved.allowImportAll) {
154
+ add("allowImport", hasExplicit(input, "allowImport"), { scoped: resolved.allowImport.length > 0 });
155
+ }
156
+ if (!resolved.env.allowAll) {
157
+ add("env", hasExplicit(envInput, "allow"), {
158
+ scoped: resolved.env.allow.length > 0,
159
+ });
160
+ }
161
+ if (resolved.env.deny.length > 0) {
162
+ add("denyEnv", hasNonEmptyArray(envInput, "deny"));
163
+ }
164
+ if (!resolved.runAll) {
165
+ const explicitRun = hasExplicit(input, "run") ||
166
+ (target.runtime === "node" &&
167
+ hasExplicit(nodeInput, "allowChildProcess")) ||
168
+ (target.runtime === "deno" &&
169
+ hasExplicit(denoInput, "allowRun"));
170
+ add("run", explicitRun, { scoped: resolved.run.length > 0 });
171
+ }
172
+ if (resolved.denyRun.length > 0) {
173
+ add("denyRun", hasNonEmptyArray(input, "denyRun"));
174
+ }
175
+ if (!resolved.workers) {
176
+ const explicitWorkers = hasExplicit(input, "workers") ||
177
+ (target.runtime === "node" &&
178
+ hasExplicit(nodeInput, "allowWorker"));
179
+ add("workers", explicitWorkers);
180
+ }
181
+ if (!resolved.ffiAll) {
182
+ add("ffi", hasExplicit(input, "ffi"), {
183
+ scoped: resolved.ffi.length > 0,
184
+ processNativeTransport: true,
185
+ });
186
+ }
187
+ if (resolved.denyFfi.length > 0) {
188
+ add("denyFfi", hasNonEmptyArray(input, "denyFfi"), {
189
+ processNativeTransport: !resolved.ffiAll,
190
+ });
191
+ }
192
+ if (!resolved.sysAll) {
193
+ add("sys", hasExplicit(input, "sys"), {
194
+ scoped: resolved.sys.length > 0,
195
+ });
196
+ }
197
+ if (resolved.denySys.length > 0) {
198
+ add("denySys", hasNonEmptyArray(input, "denySys"));
199
+ }
200
+ if (!resolved.wasi) {
201
+ const explicitWasi = hasExplicit(input, "wasi") ||
202
+ (target.runtime === "node" &&
203
+ hasExplicit(nodeInput, "allowWasi"));
204
+ add("wasi", explicitWasi);
205
+ }
206
+ if (target.runtime === "node" && nodeInput !== undefined) {
207
+ if (hasOwn(nodeInput, "allowAddons") && nodeInput.allowAddons === false) {
208
+ add("node.allowAddons", true);
209
+ }
210
+ if (hasOwn(nodeInput, "allowFfi") && nodeInput.allowFfi === false) {
211
+ add("node.allowFfi", true);
212
+ }
213
+ }
214
+ return { target, checks };
215
+ };
216
+ const warningKeys = new Set();
217
+ export const enforceProcessPermissionCompatibility = (report) => {
218
+ const incompatible = report.checks.filter((check) => check.level !== "exact");
219
+ const explicit = incompatible.filter((check) => check.explicit);
220
+ const label = targetLabel(report.target);
221
+ if (explicit.length > 0) {
222
+ const details = explicit
223
+ .map((check) => `permission.${check.permission} is ${check.level}: ${check.reason}`)
224
+ .join(" ");
225
+ throw new Error(`knitting: ${label} process workers cannot enforce the requested permission policy. ` +
226
+ `${details} Choose a runtime that can represent the restriction, add an OS sandbox, ` +
227
+ `or remove the restriction only if broader access is acceptable.`);
228
+ }
229
+ if (incompatible.length === 0)
230
+ return;
231
+ const names = [...new Set(incompatible.map((check) => check.permission))];
232
+ const warningKey = label;
233
+ if (warningKeys.has(warningKey))
234
+ return;
235
+ warningKeys.add(warningKey);
236
+ console.warn(`knitting: ${label} process workers cannot enforce every implicit strict ` +
237
+ `permission default (${names.join(", ")}). Explicit unsupported ` +
238
+ `restrictions fail closed; use an OS sandbox for hostile task code.`);
239
+ };
@@ -1,2 +1,4 @@
1
+ export { classifyProcessPermissionCompatibility, enforceProcessPermissionCompatibility, } from "./compatibility.js";
1
2
  export { resolvePermissionProtocol, toRuntimePermissionFlags, } from "./protocol.js";
3
+ export type { PermissionCompatibilityCheck, PermissionCompatibilityLevel, ProcessPermissionCompatibilityReport, ProcessPermissionRuntime, ProcessPermissionTarget, } from "./compatibility.js";
2
4
  export type { PermissionPath, PermissionMode, PermissionLegacyMode, SysApiName, NodePermissionSettings, DenoPermissionSettings, PermissionEnvironment, PermissionProtocol, PermissionProtocolInput, ResolvedPermissionProtocol, } from "./protocol.js";
@@ -1,2 +1,3 @@
1
+ export { classifyProcessPermissionCompatibility, enforceProcessPermissionCompatibility, } from "./compatibility.js";
1
2
  export { resolvePermissionProtocol, toRuntimePermissionFlags, } from "./protocol.js";
2
3
  /* c8 ignore end */
@@ -3,6 +3,7 @@ type NodePermissionSettings = {
3
3
  allowWorker?: boolean;
4
4
  allowChildProcess?: boolean;
5
5
  allowAddons?: boolean;
6
+ allowFfi?: boolean;
6
7
  allowWasi?: boolean;
7
8
  };
8
9
  type DenoPermissionSettings = {
@@ -1,9 +1,21 @@
1
- import { existsSync as existsSyncCompat, realpathSync as realpathSyncCompat, } from "node:fs";
2
- import { isAbsolute as pathIsAbsolute, relative as pathRelative, resolve as pathResolve, } from "node:path";
3
- import { fileURLToPath as fileURLToPathCompat } from "node:url";
4
- import { RUNTIME } from "../common/runtime.js";
1
+ import { IS_BROWSER, RUNTIME } from "../common/runtime.js";
5
2
  import { toCanonicalPath as toSharedCanonicalPath } from "../common/path-canonical.js";
6
- import { getNodeProcess } from "../common/node-compat.js";
3
+ import { getNodeBuiltinModule, getNodeProcess } from "../common/node-compat.js";
4
+ const requireNodeModule = (specifier) => {
5
+ const module = getNodeBuiltinModule(specifier);
6
+ if (module === undefined) {
7
+ throw new Error(`${specifier} is not available in this runtime`);
8
+ }
9
+ return module;
10
+ };
11
+ const nodeFs = () => requireNodeModule("node:fs");
12
+ const nodePath = () => requireNodeModule("node:path");
13
+ const nodeUrl = () => requireNodeModule("node:url");
14
+ const existsSyncCompat = (path) => nodeFs().existsSync(path);
15
+ const pathIsAbsolute = (path) => nodePath().isAbsolute(path);
16
+ const pathRelative = (from, to) => nodePath().relative(from, to);
17
+ const pathResolve = (...segments) => nodePath().resolve(...segments);
18
+ const fileURLToPathCompat = (url) => nodeUrl().fileURLToPath(url);
7
19
  const DEFAULT_ENV_FILE = ".env";
8
20
  const DEFAULT_DENO_LOCK_FILE = "deno.lock";
9
21
  const NODE_MODULES_DIR = "node_modules";
@@ -209,7 +221,10 @@ const toEnvFiles = (input, cwd, home) => {
209
221
  : [DEFAULT_ENV_FILE];
210
222
  return toUniquePathList(values, cwd, home);
211
223
  };
212
- const rawRealpathSync = realpathSyncCompat.native ?? realpathSyncCompat;
224
+ const rawRealpathSync = (candidate) => {
225
+ const { realpathSync } = nodeFs();
226
+ return (realpathSync.native ?? realpathSync)(candidate);
227
+ };
213
228
  const toCanonicalPath = (candidate) => {
214
229
  return toSharedCanonicalPath(candidate, {
215
230
  existsSync: existsSyncCompat,
@@ -276,26 +291,45 @@ const resolveNodePermissionActivationFlag = () => {
276
291
  }
277
292
  return "--permission";
278
293
  };
279
- const toNodeFlags = ({ read, readAll, write, writeAll, envFiles, node, }) => {
294
+ const nodeSupportsNetworkPermission = () => {
295
+ try {
296
+ const raw = getNodeProcess()?.versions?.node;
297
+ const major = Number.parseInt(String(raw).split(".", 1)[0] ?? "", 10);
298
+ return Number.isInteger(major) && major >= 25;
299
+ }
300
+ catch {
301
+ return false;
302
+ }
303
+ };
304
+ const toNodeFlags = ({ read, readAll, write, writeAll, netAll, envFiles, node, }) => {
280
305
  const modelFlags = [];
281
306
  if (readAll) {
282
307
  modelFlags.push("--allow-fs-read=*");
283
308
  }
284
- else if (read.length > 0) {
285
- modelFlags.push(`--allow-fs-read=${read.join(",")}`);
309
+ else {
310
+ for (const path of read) {
311
+ modelFlags.push(`--allow-fs-read=${path}`);
312
+ }
286
313
  }
287
314
  if (writeAll) {
288
315
  modelFlags.push("--allow-fs-write=*");
289
316
  }
290
- else if (write.length > 0) {
291
- modelFlags.push(`--allow-fs-write=${write.join(",")}`);
317
+ else {
318
+ for (const path of write) {
319
+ modelFlags.push(`--allow-fs-write=${path}`);
320
+ }
292
321
  }
293
322
  if (node.allowWorker)
294
323
  modelFlags.push("--allow-worker");
295
324
  if (node.allowChildProcess)
296
325
  modelFlags.push("--allow-child-process");
326
+ if (netAll && nodeSupportsNetworkPermission()) {
327
+ modelFlags.push("--allow-net");
328
+ }
297
329
  if (node.allowAddons)
298
330
  modelFlags.push("--allow-addons");
331
+ if (node.allowFfi)
332
+ modelFlags.push("--allow-ffi");
299
333
  if (node.allowWasi)
300
334
  modelFlags.push("--allow-wasi");
301
335
  const flags = [];
@@ -402,6 +436,10 @@ export const resolvePermissionProtocol = ({ permission, modules, }) => {
402
436
  const input = normalizeProtocolInput(permission);
403
437
  if (!input)
404
438
  return undefined;
439
+ // Browsers have no filesystem, process, or runtime flags to police, and the
440
+ // strict default would otherwise reach for `node:path` and throw.
441
+ if (IS_BROWSER)
442
+ return undefined;
405
443
  const rawMode = input.mode;
406
444
  const mode = (rawMode === "unsafe" || rawMode === "off")
407
445
  ? "unsafe"
@@ -458,6 +496,7 @@ export const resolvePermissionProtocol = ({ permission, modules, }) => {
458
496
  allowWorker: true,
459
497
  allowChildProcess: true,
460
498
  allowAddons: true,
499
+ allowFfi: true,
461
500
  allowWasi: true,
462
501
  flags: [],
463
502
  },
@@ -534,9 +573,7 @@ export const resolvePermissionProtocol = ({ permission, modules, }) => {
534
573
  const workers = hasOwn(input, "workers")
535
574
  ? input.workers === true
536
575
  : input.node?.allowWorker === true;
537
- const ffiSource = hasOwn(input, "ffi")
538
- ? input.ffi
539
- : (input.node?.allowAddons === true ? true : false);
576
+ const ffiSource = hasOwn(input, "ffi") ? input.ffi : false;
540
577
  const ffiAll = ffiSource === true;
541
578
  const ffi = ffiAll ? [] : toUniquePathList(Array.isArray(ffiSource) ? ffiSource : undefined, cwd, home);
542
579
  const denyFfi = toUniquePathList(input.denyFfi, cwd, home);
@@ -552,7 +589,13 @@ export const resolvePermissionProtocol = ({ permission, modules, }) => {
552
589
  const nodeSettings = {
553
590
  allowWorker: workers,
554
591
  allowChildProcess: runAll || run.length > 0,
555
- allowAddons: ffiAll || ffi.length > 0,
592
+ // Top-level `ffi` is the explicit cross-runtime native-code capability.
593
+ // Runtime-specific overrides stay independent so allowing addons does not
594
+ // silently grant unrestricted node:ffi access.
595
+ allowAddons: input.node?.allowAddons === true ||
596
+ ffiAll ||
597
+ ffi.length > 0,
598
+ allowFfi: input.node?.allowFfi === true || ffiAll || ffi.length > 0,
556
599
  allowWasi: wasi,
557
600
  };
558
601
  const denoSettings = {
@@ -604,6 +647,7 @@ export const resolvePermissionProtocol = ({ permission, modules, }) => {
604
647
  readAll,
605
648
  write: resolvedWrite,
606
649
  writeAll,
650
+ netAll,
607
651
  envFiles,
608
652
  node: nodeSettings,
609
653
  }),
@@ -0,0 +1,21 @@
1
+ import type { CompiledWorkerCheck, CompiledWorkerOptions } from "../types.js";
2
+ export declare const COMPILED_WORKER_FORMAT = "knitting-compiled-worker";
3
+ export declare const COMPILED_WORKER_FORMAT_VERSION = 1;
4
+ export declare const COMPILED_WORKER_PROTOCOL = "knitting-number-v1";
5
+ export declare const COMPILED_WORKER_JSON_PROTOCOL = "knitting-json-v1";
6
+ export declare const COMPILED_WORKER_EXTENSION = ".knt";
7
+ export type CompiledWorkerTask = {
8
+ index: number;
9
+ exportName: string;
10
+ };
11
+ export type ArtifactInspection = CompiledWorkerCheck & {
12
+ artifactHref: string;
13
+ manifestHref: string;
14
+ taskEntries?: CompiledWorkerTask[];
15
+ };
16
+ export declare const defaultCompiledWorkerArtifact: (source: string | URL) => string;
17
+ export declare const inspectCompiledWorkerArtifact: ({ source, options, requiredTasks, }: {
18
+ source: string | URL;
19
+ options?: CompiledWorkerOptions;
20
+ requiredTasks?: readonly string[];
21
+ }) => ArtifactInspection;
@@ -0,0 +1,260 @@
1
+ import { getNodeBuiltinModule, getNodeProcess } from "../common/node-compat.js";
2
+ import { toModuleUrl } from "../common/module-url.js";
3
+ export const COMPILED_WORKER_FORMAT = "knitting-compiled-worker";
4
+ export const COMPILED_WORKER_FORMAT_VERSION = 1;
5
+ export const COMPILED_WORKER_PROTOCOL = "knitting-number-v1";
6
+ export const COMPILED_WORKER_JSON_PROTOCOL = "knitting-json-v1";
7
+ export const COMPILED_WORKER_EXTENSION = ".knt";
8
+ const deno = globalThis.Deno;
9
+ const normalizeArch = (arch) => {
10
+ if (arch === "x86_64")
11
+ return "x64";
12
+ if (arch === "aarch64")
13
+ return "arm64";
14
+ return arch ?? "unknown";
15
+ };
16
+ const runtimeTarget = () => {
17
+ const process = getNodeProcess();
18
+ if (process !== undefined) {
19
+ return {
20
+ platform: process.platform ?? "unknown",
21
+ arch: normalizeArch(process.arch),
22
+ };
23
+ }
24
+ return {
25
+ platform: deno?.build?.os ?? "unknown",
26
+ arch: normalizeArch(deno?.build?.arch),
27
+ };
28
+ };
29
+ const normalizeSourceHref = (source) => source instanceof URL ? source.href : toModuleUrl(source);
30
+ const resolveHref = (value, base) => {
31
+ try {
32
+ return new URL(value).href;
33
+ }
34
+ catch {
35
+ return new URL(value, base).href;
36
+ }
37
+ };
38
+ export const defaultCompiledWorkerArtifact = (source) => {
39
+ const url = new URL(normalizeSourceHref(source));
40
+ url.search = "";
41
+ url.hash = "";
42
+ url.pathname = url.pathname.replace(/\.[^./]+$/, "") +
43
+ COMPILED_WORKER_EXTENSION;
44
+ return url.href;
45
+ };
46
+ const artifactHrefs = (source, options = {}) => {
47
+ const sourceHref = normalizeSourceHref(source);
48
+ const artifactHref = options.artifact === undefined
49
+ ? defaultCompiledWorkerArtifact(sourceHref)
50
+ : resolveHref(options.artifact, sourceHref);
51
+ const manifestHref = options.manifest === undefined
52
+ ? artifactHref + ".json"
53
+ : resolveHref(options.manifest, artifactHref);
54
+ return { artifactHref, manifestHref };
55
+ };
56
+ const filePath = (href) => {
57
+ const converter = getNodeBuiltinModule("node:url")?.fileURLToPath;
58
+ if (converter !== undefined)
59
+ return converter(href);
60
+ const url = new URL(href);
61
+ if (url.protocol !== "file:") {
62
+ throw new Error("Compiled worker artifact must use file:, got " + url.protocol);
63
+ }
64
+ return decodeURIComponent(url.pathname);
65
+ };
66
+ const readTextFile = (path) => {
67
+ const fs = getNodeBuiltinModule("node:fs");
68
+ if (fs !== undefined)
69
+ return fs.readFileSync(path, "utf8");
70
+ if (deno?.readTextFileSync !== undefined)
71
+ return deno.readTextFileSync(path);
72
+ throw new Error("Synchronous filesystem access is unavailable");
73
+ };
74
+ const inspectExecutable = (path) => {
75
+ try {
76
+ const fs = getNodeBuiltinModule("node:fs");
77
+ if (fs !== undefined) {
78
+ const stat = fs.statSync(path);
79
+ if (!stat.isFile())
80
+ return "artifact is not a file";
81
+ if (runtimeTarget().platform !== "win32" && (stat.mode & 0o111) === 0) {
82
+ return "artifact is not executable";
83
+ }
84
+ return undefined;
85
+ }
86
+ if (deno?.statSync !== undefined) {
87
+ const stat = deno.statSync(path);
88
+ if (!stat.isFile)
89
+ return "artifact is not a file";
90
+ if (stat.mode !== null && (stat.mode & 0o111) === 0) {
91
+ return "artifact is not executable";
92
+ }
93
+ return undefined;
94
+ }
95
+ return "filesystem metadata is unavailable";
96
+ }
97
+ catch (error) {
98
+ const message = String(error?.message ?? error);
99
+ return "artifact is unavailable: " + message;
100
+ }
101
+ };
102
+ const compilerLabel = (compiler) => {
103
+ if (typeof compiler === "string" && compiler.length > 0)
104
+ return compiler;
105
+ if (compiler !== null && typeof compiler === "object" &&
106
+ typeof compiler.name === "string" &&
107
+ compiler.name.length > 0 &&
108
+ typeof compiler.version === "string") {
109
+ const value = compiler;
110
+ return (value.name + " " + value.version).trim();
111
+ }
112
+ return undefined;
113
+ };
114
+ export const inspectCompiledWorkerArtifact = ({ source, options, requiredTasks, }) => {
115
+ const sourceHref = normalizeSourceHref(source);
116
+ const { artifactHref, manifestHref } = artifactHrefs(sourceHref, options);
117
+ let artifact;
118
+ let manifest;
119
+ try {
120
+ artifact = filePath(artifactHref);
121
+ manifest = filePath(manifestHref);
122
+ }
123
+ catch (error) {
124
+ const message = String(error?.message ?? error);
125
+ return {
126
+ compiled: false,
127
+ artifact: artifactHref,
128
+ manifest: manifestHref,
129
+ artifactHref,
130
+ manifestHref,
131
+ reason: message,
132
+ };
133
+ }
134
+ const base = {
135
+ compiled: false,
136
+ artifact,
137
+ manifest,
138
+ artifactHref,
139
+ manifestHref,
140
+ };
141
+ const executableProblem = inspectExecutable(artifact);
142
+ if (executableProblem !== undefined) {
143
+ return { ...base, reason: executableProblem };
144
+ }
145
+ let parsed;
146
+ try {
147
+ parsed = JSON.parse(readTextFile(manifest));
148
+ }
149
+ catch (error) {
150
+ const message = String(error?.message ?? error);
151
+ return {
152
+ ...base,
153
+ reason: "manifest is unavailable or invalid: " + message,
154
+ };
155
+ }
156
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
157
+ return { ...base, reason: "manifest root must be an object" };
158
+ }
159
+ const workerManifest = parsed;
160
+ const compiler = compilerLabel(workerManifest.compiler);
161
+ const details = {
162
+ compiler,
163
+ protocol: typeof workerManifest.protocol === "string"
164
+ ? workerManifest.protocol
165
+ : undefined,
166
+ };
167
+ if (workerManifest.format !== COMPILED_WORKER_FORMAT ||
168
+ workerManifest.version !== COMPILED_WORKER_FORMAT_VERSION) {
169
+ return { ...base, ...details, reason: "unsupported manifest format" };
170
+ }
171
+ if (workerManifest.protocol !== COMPILED_WORKER_PROTOCOL &&
172
+ workerManifest.protocol !== COMPILED_WORKER_JSON_PROTOCOL) {
173
+ return { ...base, ...details, reason: "unsupported worker protocol" };
174
+ }
175
+ if (compiler === undefined) {
176
+ return { ...base, ...details, reason: "manifest compiler is invalid" };
177
+ }
178
+ const target = runtimeTarget();
179
+ if (workerManifest.target?.platform !== target.platform ||
180
+ normalizeArch(workerManifest.target?.arch) !== target.arch) {
181
+ return {
182
+ ...base,
183
+ ...details,
184
+ reason: "artifact target " +
185
+ (workerManifest.target?.platform ?? "unknown") + "/" +
186
+ (workerManifest.target?.arch ?? "unknown") + " does not match " +
187
+ target.platform + "/" + target.arch,
188
+ };
189
+ }
190
+ if (!Array.isArray(workerManifest.tasks) || workerManifest.tasks.length === 0) {
191
+ return { ...base, ...details, reason: "manifest has no tasks" };
192
+ }
193
+ const indices = new Set();
194
+ const taskNames = new Set();
195
+ for (const task of workerManifest.tasks) {
196
+ if (!Number.isSafeInteger(task?.index) || task.index < 0 ||
197
+ task.index > 0x7fff_ffff ||
198
+ typeof task.exportName !== "string" || task.exportName.length === 0 ||
199
+ indices.has(task.index) || taskNames.has(task.exportName)) {
200
+ return { ...base, ...details, reason: "manifest task table is invalid" };
201
+ }
202
+ indices.add(task.index);
203
+ taskNames.add(task.exportName);
204
+ }
205
+ if (typeof workerManifest.source !== "string" ||
206
+ workerManifest.source.length === 0) {
207
+ return { ...base, ...details, reason: "manifest source is invalid" };
208
+ }
209
+ let manifestSource;
210
+ try {
211
+ manifestSource = resolveHref(workerManifest.source.replace(/\\/g, "/"), artifactHref);
212
+ }
213
+ catch {
214
+ return { ...base, ...details, reason: "manifest source is invalid" };
215
+ }
216
+ if (manifestSource !== sourceHref) {
217
+ return {
218
+ ...base,
219
+ ...details,
220
+ tasks: [...taskNames],
221
+ reason: "manifest was built for a different task module",
222
+ };
223
+ }
224
+ if (Number.isFinite(workerManifest.sourceMtimeMs)) {
225
+ try {
226
+ const sourcePath = filePath(sourceHref);
227
+ const fs = getNodeBuiltinModule("node:fs");
228
+ const currentMtime = fs?.statSync(sourcePath).mtimeMs ??
229
+ deno?.statSync?.(sourcePath).mtime?.getTime();
230
+ if (currentMtime !== undefined &&
231
+ currentMtime > workerManifest.sourceMtimeMs + 0.5) {
232
+ return {
233
+ ...base,
234
+ ...details,
235
+ tasks: [...taskNames],
236
+ reason: "task module changed after the artifact was built",
237
+ };
238
+ }
239
+ }
240
+ catch {
241
+ return { ...base, ...details, reason: "task module is unavailable" };
242
+ }
243
+ }
244
+ const missing = requiredTasks?.filter((name) => !taskNames.has(name)) ?? [];
245
+ if (missing.length > 0) {
246
+ return {
247
+ ...base,
248
+ ...details,
249
+ tasks: [...taskNames],
250
+ reason: "compiled artifact is missing tasks: " + missing.join(", "),
251
+ };
252
+ }
253
+ return {
254
+ ...base,
255
+ ...details,
256
+ compiled: true,
257
+ tasks: [...taskNames],
258
+ taskEntries: workerManifest.tasks,
259
+ };
260
+ };