veryfront 0.1.74 → 0.1.76

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 (122) hide show
  1. package/README.md +2 -0
  2. package/esm/cli/commands/files/command.d.ts +3 -3
  3. package/esm/cli/commands/knowledge/command.d.ts +2 -0
  4. package/esm/cli/commands/knowledge/command.d.ts.map +1 -1
  5. package/esm/cli/commands/knowledge/command.js +64 -1
  6. package/esm/deno.d.ts +7 -0
  7. package/esm/deno.js +13 -6
  8. package/esm/src/data/data-fetcher.d.ts +11 -1
  9. package/esm/src/data/data-fetcher.d.ts.map +1 -1
  10. package/esm/src/data/data-fetcher.js +5 -2
  11. package/esm/src/data/index.d.ts +1 -1
  12. package/esm/src/data/index.d.ts.map +1 -1
  13. package/esm/src/data/server-data-fetcher.d.ts +14 -1
  14. package/esm/src/data/server-data-fetcher.d.ts.map +1 -1
  15. package/esm/src/data/server-data-fetcher.js +65 -3
  16. package/esm/src/jobs/index.d.ts +34 -0
  17. package/esm/src/jobs/index.d.ts.map +1 -0
  18. package/esm/src/jobs/index.js +33 -0
  19. package/esm/src/jobs/jobs-client.d.ts +134 -0
  20. package/esm/src/jobs/jobs-client.d.ts.map +1 -0
  21. package/esm/src/jobs/jobs-client.js +218 -0
  22. package/esm/src/jobs/schemas.d.ts +1304 -0
  23. package/esm/src/jobs/schemas.d.ts.map +1 -0
  24. package/esm/src/jobs/schemas.js +159 -0
  25. package/esm/src/platform/adapters/veryfront-api-client/retry-handler.d.ts +4 -0
  26. package/esm/src/platform/adapters/veryfront-api-client/retry-handler.d.ts.map +1 -1
  27. package/esm/src/platform/adapters/veryfront-api-client/retry-handler.js +12 -6
  28. package/esm/src/proxy/handler.d.ts.map +1 -1
  29. package/esm/src/proxy/handler.js +21 -21
  30. package/esm/src/rendering/orchestrator/lifecycle.d.ts +4 -0
  31. package/esm/src/rendering/orchestrator/lifecycle.d.ts.map +1 -1
  32. package/esm/src/rendering/orchestrator/lifecycle.js +8 -0
  33. package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
  34. package/esm/src/rendering/orchestrator/pipeline.js +6 -1
  35. package/esm/src/rendering/orchestrator/ssr-orchestrator.d.ts +26 -1
  36. package/esm/src/rendering/orchestrator/ssr-orchestrator.d.ts.map +1 -1
  37. package/esm/src/rendering/orchestrator/ssr-orchestrator.js +77 -1
  38. package/esm/src/routing/api/handler.d.ts.map +1 -1
  39. package/esm/src/routing/api/handler.js +6 -2
  40. package/esm/src/routing/api/route-executor.d.ts +8 -2
  41. package/esm/src/routing/api/route-executor.d.ts.map +1 -1
  42. package/esm/src/routing/api/route-executor.js +158 -3
  43. package/esm/src/security/deno-permissions.d.ts +7 -1
  44. package/esm/src/security/deno-permissions.d.ts.map +1 -1
  45. package/esm/src/security/deno-permissions.js +12 -1
  46. package/esm/src/security/sandbox/project-worker.d.ts +61 -0
  47. package/esm/src/security/sandbox/project-worker.d.ts.map +1 -0
  48. package/esm/src/security/sandbox/project-worker.js +318 -0
  49. package/esm/src/security/sandbox/worker-permissions.d.ts +30 -0
  50. package/esm/src/security/sandbox/worker-permissions.d.ts.map +1 -0
  51. package/esm/src/security/sandbox/worker-permissions.js +63 -0
  52. package/esm/src/security/sandbox/worker-pool.d.ts +87 -0
  53. package/esm/src/security/sandbox/worker-pool.d.ts.map +1 -0
  54. package/esm/src/security/sandbox/worker-pool.js +359 -0
  55. package/esm/src/security/sandbox/worker-types.d.ts +167 -0
  56. package/esm/src/security/sandbox/worker-types.d.ts.map +1 -0
  57. package/esm/src/security/sandbox/worker-types.js +19 -0
  58. package/esm/src/server/handlers/request/internal-tasks-list.handler.d.ts +11 -0
  59. package/esm/src/server/handlers/request/internal-tasks-list.handler.d.ts.map +1 -0
  60. package/esm/src/server/handlers/request/internal-tasks-list.handler.js +72 -0
  61. package/esm/src/server/project-env/storage.d.ts +6 -0
  62. package/esm/src/server/project-env/storage.d.ts.map +1 -1
  63. package/esm/src/server/project-env/storage.js +8 -0
  64. package/esm/src/server/runtime-handler/index.d.ts +1 -1
  65. package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
  66. package/esm/src/server/runtime-handler/index.js +3 -0
  67. package/esm/src/server/runtime-handler/project-isolation.d.ts +5 -0
  68. package/esm/src/server/runtime-handler/project-isolation.d.ts.map +1 -1
  69. package/esm/src/server/runtime-handler/project-isolation.js +44 -0
  70. package/esm/src/server/shared/renderer/memory/pressure.d.ts +7 -0
  71. package/esm/src/server/shared/renderer/memory/pressure.d.ts.map +1 -1
  72. package/esm/src/server/shared/renderer/memory/pressure.js +7 -0
  73. package/esm/src/task/control-plane.d.ts +105 -0
  74. package/esm/src/task/control-plane.d.ts.map +1 -0
  75. package/esm/src/task/control-plane.js +52 -0
  76. package/esm/src/task/types.d.ts +6 -0
  77. package/esm/src/task/types.d.ts.map +1 -1
  78. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.d.ts +4 -4
  79. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.d.ts.map +1 -1
  80. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.js +15 -15
  81. package/esm/src/utils/index.d.ts +10 -1
  82. package/esm/src/utils/index.d.ts.map +1 -1
  83. package/esm/src/utils/index.js +9 -1
  84. package/esm/src/utils/logger/index.d.ts +1 -1
  85. package/esm/src/utils/logger/index.d.ts.map +1 -1
  86. package/esm/src/utils/logger/index.js +1 -1
  87. package/esm/src/utils/logger/logger.d.ts +14 -0
  88. package/esm/src/utils/logger/logger.d.ts.map +1 -1
  89. package/esm/src/utils/logger/logger.js +17 -0
  90. package/esm/src/workflow/claude-code/tool.d.ts +5 -5
  91. package/package.json +8 -1
  92. package/src/cli/commands/knowledge/command.ts +76 -1
  93. package/src/deno.js +13 -6
  94. package/src/src/data/data-fetcher.ts +18 -2
  95. package/src/src/data/index.ts +1 -1
  96. package/src/src/data/server-data-fetcher.ts +106 -3
  97. package/src/src/jobs/index.ts +85 -0
  98. package/src/src/jobs/jobs-client.ts +503 -0
  99. package/src/src/jobs/schemas.ts +202 -0
  100. package/src/src/platform/adapters/veryfront-api-client/retry-handler.ts +15 -6
  101. package/src/src/proxy/handler.ts +27 -19
  102. package/src/src/rendering/orchestrator/lifecycle.ts +11 -0
  103. package/src/src/rendering/orchestrator/pipeline.ts +7 -2
  104. package/src/src/rendering/orchestrator/ssr-orchestrator.ts +119 -0
  105. package/src/src/routing/api/handler.ts +16 -3
  106. package/src/src/routing/api/route-executor.ts +258 -1
  107. package/src/src/security/deno-permissions.ts +13 -1
  108. package/src/src/security/sandbox/project-worker.ts +416 -0
  109. package/src/src/security/sandbox/worker-permissions.ts +77 -0
  110. package/src/src/security/sandbox/worker-pool.ts +459 -0
  111. package/src/src/security/sandbox/worker-types.ts +212 -0
  112. package/src/src/server/handlers/request/internal-tasks-list.handler.ts +103 -0
  113. package/src/src/server/project-env/storage.ts +9 -0
  114. package/src/src/server/runtime-handler/index.ts +3 -0
  115. package/src/src/server/runtime-handler/project-isolation.ts +53 -0
  116. package/src/src/server/shared/renderer/memory/pressure.ts +8 -0
  117. package/src/src/task/control-plane.ts +76 -0
  118. package/src/src/task/types.ts +6 -0
  119. package/src/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.ts +18 -12
  120. package/src/src/utils/index.ts +11 -0
  121. package/src/src/utils/logger/index.ts +1 -0
  122. package/src/src/utils/logger/logger.ts +34 -0
@@ -17,8 +17,9 @@ export const SERVER_PERMISSIONS = [
17
17
  "--allow-net",
18
18
  "--allow-env",
19
19
  "--allow-run",
20
- "--allow-ffi",
21
20
  "--allow-sys",
21
+ "--unstable-worker-options",
22
+ "--unstable-net",
22
23
  ] as const;
23
24
 
24
25
  /**
@@ -41,3 +42,14 @@ export const BUILD_HELPER_PERMISSIONS = [
41
42
  "--allow-write",
42
43
  "--allow-env",
43
44
  ] as const;
45
+
46
+ /**
47
+ * RENDER_WORKER — Per-project Worker for isolated code execution.
48
+ * Read-only filesystem (transformed modules), network (data fetchers),
49
+ * env (API keys and config). No subprocess/ffi/sys.
50
+ */
51
+ export const RENDER_WORKER_PERMISSIONS = [
52
+ "--allow-read",
53
+ "--allow-net",
54
+ "--allow-env",
55
+ ] as const;
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Project Worker
3
+ *
4
+ * Wraps a single Deno Worker for one project. Manages the Worker lifecycle,
5
+ * sends/receives structured messages, enforces per-request timeouts,
6
+ * and serializes errors across the Worker boundary.
7
+ *
8
+ * @module security/sandbox/project-worker
9
+ */
10
+ import * as dntShim from "../../../_dnt.shims.js";
11
+
12
+
13
+ import { serverLogger } from "../../utils/index.js";
14
+ import { isCompiledBinary } from "../../utils/index.js";
15
+ import { withSpan } from "../../observability/tracing/otlp-setup.js";
16
+ import { TIMEOUT_ERROR, UNKNOWN_ERROR } from "../../errors/index.js";
17
+ import type { WorkerPermissions } from "./worker-permissions.js";
18
+ import type {
19
+ WorkerRequest,
20
+ WorkerResponse,
21
+ WorkerStreamChunk,
22
+ WorkerStreamEnd,
23
+ } from "./worker-types.js";
24
+
25
+ const logger = serverLogger.component("project-worker");
26
+ const textEncoder = new TextEncoder();
27
+
28
+ type ExtendedWorkerOptions = {
29
+ type: "module";
30
+ name?: string;
31
+ deno?: { permissions: WorkerPermissions };
32
+ };
33
+
34
+ export interface ProjectWorkerOptions {
35
+ projectId: string;
36
+ permissions: WorkerPermissions;
37
+ requestTimeoutMs: number;
38
+ }
39
+
40
+ interface PendingRequest {
41
+ resolve: (value: WorkerResponse) => void;
42
+ reject: (error: Error) => void;
43
+ timer: ReturnType<typeof dntShim.setTimeout>;
44
+ }
45
+
46
+ interface StreamHandler {
47
+ onChunk: (chunk: Uint8Array) => void;
48
+ onEnd: () => void;
49
+ onError: (error: Error) => void;
50
+ }
51
+
52
+ /**
53
+ * Status of a project worker.
54
+ */
55
+ export type WorkerStatus = "idle" | "busy" | "crashed" | "terminated";
56
+
57
+ export class ProjectWorker {
58
+ readonly projectId: string;
59
+
60
+ private worker: Worker | null = null;
61
+ private pending = new Map<string, PendingRequest>();
62
+ private streamHandlers = new Map<string, StreamHandler>();
63
+ private requestTimeoutMs: number;
64
+ private permissions: WorkerPermissions;
65
+ private _requestCount = 0;
66
+ private _lastActivityAt = Date.now();
67
+ private _status: WorkerStatus = "idle";
68
+
69
+ constructor(options: ProjectWorkerOptions) {
70
+ this.projectId = options.projectId;
71
+ this.permissions = options.permissions;
72
+ this.requestTimeoutMs = options.requestTimeoutMs;
73
+ }
74
+
75
+ get status(): WorkerStatus {
76
+ return this._status;
77
+ }
78
+
79
+ get requestCount(): number {
80
+ return this._requestCount;
81
+ }
82
+
83
+ get lastActivityAt(): number {
84
+ return this._lastActivityAt;
85
+ }
86
+
87
+ get hasPendingRequests(): boolean {
88
+ return this.pending.size > 0;
89
+ }
90
+
91
+ /**
92
+ * Start the worker. Idempotent — safe to call if already running.
93
+ */
94
+ start(): void {
95
+ if (this.worker) return;
96
+
97
+ const workerUrl = this.getWorkerScriptUrl();
98
+
99
+ const workerOptions: ExtendedWorkerOptions = {
100
+ type: "module",
101
+ name: `project-worker-${this.projectId}`,
102
+ deno: { permissions: this.permissions },
103
+ };
104
+
105
+ // @ts-ignore - Deno Worker accepts extended options
106
+ this.worker = new Worker(workerUrl, workerOptions);
107
+ this._status = "idle";
108
+
109
+ this.worker.onmessage = (event: MessageEvent) => {
110
+ this.handleMessage(event.data);
111
+ };
112
+
113
+ this.worker.onerror = (event) => {
114
+ logger.error("Worker error", {
115
+ projectId: this.projectId,
116
+ error: event.message ?? String(event),
117
+ });
118
+ this._status = "crashed";
119
+ this.rejectAllPending("Worker crashed");
120
+ };
121
+
122
+ logger.debug("Worker started", { projectId: this.projectId });
123
+ }
124
+
125
+ /**
126
+ * Execute a request in this worker. Returns a typed response.
127
+ */
128
+ execute(request: WorkerRequest): Promise<WorkerResponse> {
129
+ return withSpan(
130
+ "worker.execute",
131
+ () => {
132
+ if (!this.worker || this._status === "crashed" || this._status === "terminated") {
133
+ return Promise.reject(
134
+ UNKNOWN_ERROR.create({ detail: `Worker not available (status: ${this._status})` }),
135
+ );
136
+ }
137
+
138
+ this._requestCount++;
139
+ this._lastActivityAt = Date.now();
140
+ this._status = "busy";
141
+
142
+ return new Promise<WorkerResponse>((resolve, reject) => {
143
+ const timer = dntShim.setTimeout(() => {
144
+ this.pending.delete(request.id);
145
+ this.updateIdleStatus();
146
+ reject(
147
+ TIMEOUT_ERROR.create({
148
+ detail: `Worker request timed out after ${this.requestTimeoutMs}ms`,
149
+ }),
150
+ );
151
+ }, this.requestTimeoutMs);
152
+
153
+ this.pending.set(request.id, { resolve, reject, timer });
154
+ this.worker!.postMessage(request);
155
+ });
156
+ },
157
+ {
158
+ "worker.projectId": this.projectId,
159
+ "worker.requestType": request.type,
160
+ "worker.requestId": request.id,
161
+ },
162
+ );
163
+ }
164
+
165
+ /**
166
+ * Execute a streaming request. Returns a ReadableStream that yields
167
+ * chunks as they arrive from the Worker via postMessage.
168
+ *
169
+ * Used for streaming SSR where the Worker sends chunks progressively.
170
+ * Falls back to a single-chunk stream if the Worker returns a non-streaming
171
+ * response (ssr-result with full HTML).
172
+ */
173
+ executeStream(request: WorkerRequest): ReadableStream<Uint8Array> {
174
+ if (!this.worker || this._status === "crashed" || this._status === "terminated") {
175
+ throw UNKNOWN_ERROR.create({ detail: `Worker not available (status: ${this._status})` });
176
+ }
177
+
178
+ this._requestCount++;
179
+ this._lastActivityAt = Date.now();
180
+ this._status = "busy";
181
+
182
+ const requestId = request.id;
183
+
184
+ return new ReadableStream<Uint8Array>({
185
+ start: (controller) => {
186
+ let timer = dntShim.setTimeout(() => {
187
+ this.streamHandlers.delete(requestId);
188
+ this.pending.delete(requestId);
189
+ this.updateIdleStatus();
190
+ controller.error(
191
+ TIMEOUT_ERROR.create({
192
+ detail: `Worker stream timed out after ${this.requestTimeoutMs}ms`,
193
+ }),
194
+ );
195
+ }, this.requestTimeoutMs);
196
+
197
+ const resetTimer = () => {
198
+ clearTimeout(timer);
199
+ timer = dntShim.setTimeout(() => {
200
+ this.streamHandlers.delete(requestId);
201
+ this.pending.delete(requestId);
202
+ this.updateIdleStatus();
203
+ controller.error(
204
+ TIMEOUT_ERROR.create({
205
+ detail: `Worker stream timed out after ${this.requestTimeoutMs}ms`,
206
+ }),
207
+ );
208
+ }, this.requestTimeoutMs);
209
+ };
210
+
211
+ // Register a stream handler for this request
212
+ this.streamHandlers.set(requestId, {
213
+ onChunk: (chunk: Uint8Array) => {
214
+ resetTimer();
215
+ controller.enqueue(chunk);
216
+ },
217
+ onEnd: () => {
218
+ clearTimeout(timer);
219
+ this.streamHandlers.delete(requestId);
220
+ this.pending.delete(requestId);
221
+ this.updateIdleStatus();
222
+ controller.close();
223
+ },
224
+ onError: (error: Error) => {
225
+ clearTimeout(timer);
226
+ this.streamHandlers.delete(requestId);
227
+ this.pending.delete(requestId);
228
+ this.updateIdleStatus();
229
+ controller.error(error);
230
+ },
231
+ });
232
+
233
+ // Also register in pending for non-streaming responses (fallback)
234
+ this.pending.set(requestId, {
235
+ resolve: (response) => {
236
+ clearTimeout(timer);
237
+ this.streamHandlers.delete(requestId);
238
+ this.pending.delete(requestId);
239
+ this.updateIdleStatus();
240
+
241
+ // If we get an ssr-result, emit it as a single chunk
242
+ if (response.type === "ssr-result") {
243
+ controller.enqueue(textEncoder.encode(response.html));
244
+ controller.close();
245
+ } else if (response.type === "error") {
246
+ const err = new Error(response.error.message);
247
+ err.name = response.error.name;
248
+ controller.error(err);
249
+ } else {
250
+ controller.close();
251
+ }
252
+ },
253
+ reject: (error) => {
254
+ clearTimeout(timer);
255
+ this.streamHandlers.delete(requestId);
256
+ this.pending.delete(requestId);
257
+ this.updateIdleStatus();
258
+ controller.error(error);
259
+ },
260
+ timer,
261
+ });
262
+
263
+ this.worker!.postMessage(request);
264
+ },
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Health check — send a ping and wait for pong.
270
+ */
271
+ async isHealthy(timeoutMs = 5_000): Promise<boolean> {
272
+ if (!this.worker || this._status === "crashed" || this._status === "terminated") {
273
+ return false;
274
+ }
275
+
276
+ const id = dntShim.crypto.randomUUID();
277
+
278
+ return new Promise<boolean>((resolve) => {
279
+ const timer = dntShim.setTimeout(() => {
280
+ this.pending.delete(id);
281
+ resolve(false);
282
+ }, timeoutMs);
283
+
284
+ this.pending.set(id, {
285
+ resolve: () => {
286
+ clearTimeout(timer);
287
+ this.pending.delete(id);
288
+ resolve(true);
289
+ },
290
+ reject: () => {
291
+ clearTimeout(timer);
292
+ this.pending.delete(id);
293
+ resolve(false);
294
+ },
295
+ timer,
296
+ });
297
+
298
+ this.worker!.postMessage({ type: "ping", id });
299
+ });
300
+ }
301
+
302
+ /**
303
+ * Clear the worker's module cache. Used for dev mode hot reload.
304
+ */
305
+ clearModuleCache(): void {
306
+ if (!this.worker || this._status === "crashed" || this._status === "terminated") return;
307
+ this.worker.postMessage({ type: "clear-cache" });
308
+ }
309
+
310
+ /**
311
+ * Terminate the worker. Rejects all pending requests.
312
+ */
313
+ terminate(): void {
314
+ if (!this.worker) return;
315
+
316
+ this._status = "terminated";
317
+ this.rejectAllPending("Worker terminated");
318
+
319
+ try {
320
+ this.worker.terminate();
321
+ } catch (error) {
322
+ logger.debug("Worker terminate failed", {
323
+ projectId: this.projectId,
324
+ error,
325
+ });
326
+ }
327
+
328
+ this.worker = null;
329
+ logger.debug("Worker terminated", { projectId: this.projectId });
330
+ }
331
+
332
+ // -----------------------------------------------------------------------
333
+ // Private
334
+ // -----------------------------------------------------------------------
335
+
336
+ private getWorkerScriptUrl(): string {
337
+ // In compiled binary mode, use a data URL because blob URLs don't work
338
+ // See: deno-sandbox.ts for the same pattern
339
+ if (isCompiledBinary()) {
340
+ // For compiled binaries, we'd need to inline the worker script.
341
+ // For now, fall through to the import.meta.resolve path which works
342
+ // in development and standard Deno execution.
343
+ }
344
+
345
+ // Use import.meta.resolve to get the absolute URL of the worker script.
346
+ // This works in both `deno run` and `deno compile` contexts.
347
+ return import.meta.resolve("./worker-script.ts");
348
+ }
349
+
350
+ private handleMessage(
351
+ data:
352
+ | WorkerResponse
353
+ | WorkerStreamChunk
354
+ | WorkerStreamEnd
355
+ | { type: "pong"; id: string },
356
+ ): void {
357
+ if (data.type === "pong") {
358
+ const pending = this.pending.get((data as { id: string }).id);
359
+ if (pending) {
360
+ clearTimeout(pending.timer);
361
+ pending.resolve(data as unknown as WorkerResponse);
362
+ this.pending.delete((data as { id: string }).id);
363
+ }
364
+ return;
365
+ }
366
+
367
+ // Handle streaming SSR chunks
368
+ if (data.type === "stream-chunk") {
369
+ const handler = this.streamHandlers.get(data.id);
370
+ if (handler) handler.onChunk(data.chunk);
371
+ return;
372
+ }
373
+
374
+ if (data.type === "stream-end") {
375
+ const handler = this.streamHandlers.get(data.id);
376
+ if (handler) handler.onEnd();
377
+ return;
378
+ }
379
+
380
+ const response = data as WorkerResponse;
381
+ const pending = this.pending.get(response.id);
382
+ if (!pending) {
383
+ logger.warn("Received response for unknown request", {
384
+ projectId: this.projectId,
385
+ id: response.id,
386
+ });
387
+ return;
388
+ }
389
+
390
+ clearTimeout(pending.timer);
391
+ this.pending.delete(response.id);
392
+ this.updateIdleStatus();
393
+
394
+ pending.resolve(response);
395
+ }
396
+
397
+ private updateIdleStatus(): void {
398
+ if (this.pending.size === 0 && this._status === "busy") {
399
+ this._status = "idle";
400
+ }
401
+ }
402
+
403
+ private rejectAllPending(reason: string): void {
404
+ for (const [id, pending] of this.pending) {
405
+ clearTimeout(pending.timer);
406
+ pending.reject(UNKNOWN_ERROR.create({ detail: reason }));
407
+ this.pending.delete(id);
408
+ }
409
+
410
+ // Clean up stream handlers
411
+ for (const [id, handler] of this.streamHandlers) {
412
+ handler.onError(UNKNOWN_ERROR.create({ detail: reason }));
413
+ this.streamHandlers.delete(id);
414
+ }
415
+ }
416
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Worker Permission Builder
3
+ *
4
+ * Builds scoped Deno Worker permissions for per-project isolation.
5
+ * Each project worker gets the minimum required permissions.
6
+ *
7
+ * @module security/sandbox/worker-permissions
8
+ */
9
+
10
+ /**
11
+ * Deno Worker permission object.
12
+ * See: https://docs.deno.com/runtime/fundamentals/permissions/
13
+ */
14
+ import * as dntShim from "../../../_dnt.shims.js";
15
+
16
+ export interface WorkerPermissions {
17
+ read: string[] | boolean;
18
+ write: boolean;
19
+ net: boolean;
20
+ env: boolean;
21
+ run: boolean;
22
+ ffi: boolean;
23
+ sys: boolean;
24
+ }
25
+
26
+ // Cache compiled binary check — Deno.execPath() is a syscall that never changes at runtime
27
+ const _isCompiledBinary = (() => {
28
+ try {
29
+ const exec = typeof dntShim.Deno !== "undefined" ? dntShim.Deno.execPath?.() : undefined;
30
+ if (!exec) return false;
31
+ const name = exec.split(/[/\\]/).pop()?.toLowerCase() ?? "";
32
+ return name !== "deno" && name !== "deno.exe";
33
+ } catch {
34
+ return false;
35
+ }
36
+ })();
37
+
38
+ /**
39
+ * Build scoped permissions for a project worker.
40
+ *
41
+ * - read: restricted to the project temp dir (transformed modules) and cache dirs
42
+ * - write: denied (workers produce output via postMessage, not filesystem)
43
+ * - net: allowed (data fetchers and API routes may call external APIs)
44
+ * - env: allowed (user code reads API keys and config from environment)
45
+ * - run: denied (no subprocess spawning from user code)
46
+ * - ffi: denied (no native code from user code)
47
+ * - sys: denied (no system info access from user code)
48
+ */
49
+ export function buildWorkerPermissions(
50
+ readPaths: string[],
51
+ ): WorkerPermissions {
52
+ // In compiled binaries, user modules import from the VFS temp dir which
53
+ // is outside the project directory. Rather than trying to enumerate all
54
+ // read paths, grant full read access — the security boundary is enforced
55
+ // by denying write/run/ffi/sys, not by restricting reads.
56
+ if (_isCompiledBinary) {
57
+ return {
58
+ read: true,
59
+ write: false,
60
+ net: true,
61
+ env: true,
62
+ run: false,
63
+ ffi: false,
64
+ sys: false,
65
+ };
66
+ }
67
+
68
+ return {
69
+ read: readPaths.length > 0 ? readPaths : false,
70
+ write: false,
71
+ net: true,
72
+ env: true,
73
+ run: false,
74
+ ffi: false,
75
+ sys: false,
76
+ };
77
+ }