@erichll/pi-sandbox 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/src/index.ts ADDED
@@ -0,0 +1,472 @@
1
+ import type {
2
+ BashOperations,
3
+ ExtensionAPI,
4
+ ExtensionContext,
5
+ } from "@earendil-works/pi-coding-agent";
6
+ import {
7
+ createBashToolDefinition,
8
+ SettingsManager,
9
+ } from "@earendil-works/pi-coding-agent";
10
+ import { getBoundaryBroker } from "@erichll/pi-auto-review/broker";
11
+ import {
12
+ approveDomainEndpoint,
13
+ approveSandboxTrap,
14
+ type HumanApproval,
15
+ } from "./approval.ts";
16
+ import {
17
+ loadPiSandboxConfig,
18
+ type SubagentProvider,
19
+ } from "./config.ts";
20
+ import {
21
+ runSandboxedCommand,
22
+ type SandboxCommandOptions,
23
+ } from "./runner.ts";
24
+ import type {
25
+ ProcessBackedSubagentManager,
26
+ ProcessBackedSubagentSession,
27
+ } from "./subagent.ts";
28
+ import { Type } from "typebox";
29
+
30
+ const EXTENSION_NAME = "pi-sandbox";
31
+ const registrations = new WeakMap<ExtensionAPI, Promise<void>>();
32
+
33
+ export type PiSandboxExtensionOptions = {
34
+ subagentProvider?: SubagentProvider;
35
+ subagentManager?: ProcessBackedSubagentManager;
36
+ createSubagentManager?: () => ProcessBackedSubagentManager;
37
+ sandbox?: Pick<SandboxCommandOptions, "broker" | "platform">;
38
+ };
39
+
40
+ function sessionId(ctx: ExtensionContext): string {
41
+ return ctx.sessionManager.getSessionId();
42
+ }
43
+
44
+ function isPiSubagentsSource(
45
+ sourceInfo: ReturnType<ExtensionAPI["getAllTools"]>[number]["sourceInfo"],
46
+ ): boolean {
47
+ const packagePattern =
48
+ /(?:^|[/\\:@])(?:@[^/\\]+[/\\])?pi-subagents(?:$|[/\\:@])/;
49
+ return [sourceInfo.source, sourceInfo.path, sourceInfo.baseDir].some(
50
+ (value) => value !== undefined && packagePattern.test(value),
51
+ );
52
+ }
53
+
54
+ function externalSubagentDiagnostic(pi: ExtensionAPI): {
55
+ message: string;
56
+ level: "info" | "warning";
57
+ } {
58
+ const active = pi.getActiveTools().includes("subagent");
59
+ const tool = pi.getAllTools().find((candidate) => candidate.name === "subagent");
60
+ if (!active || !tool) {
61
+ return {
62
+ message:
63
+ "pi-sandbox provider pi-subagents selected, but no external subagent tool is active",
64
+ level: "warning",
65
+ };
66
+ }
67
+ if (!isPiSubagentsSource(tool.sourceInfo)) {
68
+ return {
69
+ message: `pi-sandbox provider pi-subagents selected, but the active subagent tool is owned by unexpected source ${tool.sourceInfo.source} (${tool.sourceInfo.path})`,
70
+ level: "warning",
71
+ };
72
+ }
73
+ return {
74
+ message:
75
+ "pi-subagents orchestration active; pi-sandbox protects Bash execution. External workers are not yet wrapped in an outer Sandbox Runtime sandbox.",
76
+ level: "info",
77
+ };
78
+ }
79
+
80
+ function humanApproval(ctx: ExtensionContext): HumanApproval {
81
+ return async (request, reason, signal) => {
82
+ if (!ctx.hasUI) return "deny";
83
+ const target =
84
+ request.resolvedPath ??
85
+ request.path ??
86
+ request.destination ??
87
+ request.operation;
88
+ const suffix = reason ? `\n${reason}` : "";
89
+ const selected = await ctx.ui.select(
90
+ `Sandbox approval required: ${request.operation} ${target}${suffix}`,
91
+ ["Allow this exact operation once", "Deny"],
92
+ { signal },
93
+ );
94
+ return selected === "Allow this exact operation once"
95
+ ? "allow-once"
96
+ : "deny";
97
+ };
98
+ }
99
+
100
+ function sandboxOperations(
101
+ ctx: ExtensionContext,
102
+ turnIndex: () => number,
103
+ sandbox?: PiSandboxExtensionOptions["sandbox"],
104
+ ): BashOperations {
105
+ return {
106
+ exec(command, cwd, options) {
107
+ const currentSessionId = sessionId(ctx);
108
+ return runSandboxedCommand({
109
+ command,
110
+ cwd,
111
+ env: options.env,
112
+ signal: options.signal,
113
+ timeout: options.timeout,
114
+ ...sandbox,
115
+ onData: options.onData,
116
+ shellPath: SettingsManager.create(cwd).getShellPath(),
117
+ review: async (trap) => {
118
+ const result = await approveSandboxTrap(trap, {
119
+ broker: getBoundaryBroker(),
120
+ command,
121
+ cwd,
122
+ sessionId: currentSessionId,
123
+ scopeKey: `${currentSessionId}:turn:${turnIndex()}`,
124
+ signal: options.signal,
125
+ humanApproval: humanApproval(ctx),
126
+ });
127
+ if (result.action === "deny" && result.reason) {
128
+ if (ctx.hasUI) {
129
+ ctx.ui.notify(`Sandbox denied: ${result.reason}`, "warning");
130
+ }
131
+ }
132
+ return result.action;
133
+ },
134
+ reviewDomain: async (endpoint) => {
135
+ const result = await approveDomainEndpoint(endpoint, {
136
+ broker: getBoundaryBroker(),
137
+ command,
138
+ cwd,
139
+ sessionId: currentSessionId,
140
+ scopeKey: `${currentSessionId}:turn:${turnIndex()}`,
141
+ signal: options.signal,
142
+ humanApproval: humanApproval(ctx),
143
+ });
144
+ if (result.action === "deny" && result.reason && ctx.hasUI) {
145
+ ctx.ui.notify(`Domain proxy denied: ${result.reason}`, "warning");
146
+ }
147
+ return result.action;
148
+ },
149
+ });
150
+ },
151
+ };
152
+ }
153
+
154
+ async function performRegistration(
155
+ pi: ExtensionAPI,
156
+ options: PiSandboxExtensionOptions,
157
+ ): Promise<void> {
158
+ let currentTurn = 0;
159
+ const subagentProvider =
160
+ options.subagentProvider ?? loadPiSandboxConfig().subagents.provider;
161
+ const subagents =
162
+ subagentProvider === "builtin"
163
+ ? (options.subagentManager ??
164
+ options.createSubagentManager?.() ??
165
+ new (await import("./subagent.ts")).ProcessBackedSubagentManager({
166
+ maxConcurrency: 4,
167
+ maxDepth: 3,
168
+ }))
169
+ : undefined;
170
+ const cwd = process.cwd();
171
+ const localBash = createBashToolDefinition(cwd);
172
+
173
+ pi.registerTool({
174
+ ...localBash,
175
+ label: "bash (pi-sandbox)",
176
+ async execute(id, params, signal, onUpdate, ctx) {
177
+ if (!ctx) throw new Error(`${EXTENSION_NAME}: missing extension context`);
178
+ const tool = createBashToolDefinition(ctx.cwd, {
179
+ operations: sandboxOperations(ctx, () => currentTurn, options.sandbox),
180
+ });
181
+ return tool.execute(id, params, signal, onUpdate, ctx);
182
+ },
183
+ });
184
+
185
+ if (subagents)
186
+ pi.registerTool({
187
+ name: "subagent",
188
+ label: "Subagent (pi-sandbox)",
189
+ description:
190
+ "Run or manage persistent Pi worker sessions. Supports foreground/background start, follow-up, wait/status/stop, and nested handoff. Every worker process tree remains inside an independent outer Sandbox Runtime sandbox.",
191
+ executionMode: "parallel",
192
+ parameters: Type.Object({
193
+ action: Type.Optional(
194
+ Type.Union(
195
+ [
196
+ Type.Literal("start"),
197
+ Type.Literal("follow_up"),
198
+ Type.Literal("wait"),
199
+ Type.Literal("status"),
200
+ Type.Literal("stop"),
201
+ Type.Literal("handoff"),
202
+ ],
203
+ { description: "Session operation; defaults to start" },
204
+ ),
205
+ ),
206
+ task: Type.Optional(
207
+ Type.String({ description: "Task or follow-up instruction" }),
208
+ ),
209
+ sessionId: Type.Optional(
210
+ Type.String({ description: "Existing session for the operation" }),
211
+ ),
212
+ background: Type.Optional(
213
+ Type.Boolean({
214
+ description: "Return after RPC acceptance instead of waiting",
215
+ }),
216
+ ),
217
+ model: Type.Optional(
218
+ Type.String({ description: "Optional provider/model override" }),
219
+ ),
220
+ }),
221
+ async execute(_id, params, signal, onUpdate, ctx) {
222
+ const action = params.action ?? "start";
223
+ const currentSessionId = sessionId(ctx);
224
+ const requireTask = (): string => {
225
+ if (!params.task?.trim()) {
226
+ throw new Error(`subagent ${action} requires task`);
227
+ }
228
+ return params.task;
229
+ };
230
+ const requireSession = (): ProcessBackedSubagentSession => {
231
+ if (!params.sessionId) {
232
+ throw new Error(`subagent ${action} requires sessionId`);
233
+ }
234
+ return subagents.get(params.sessionId);
235
+ };
236
+ const update = (session: ProcessBackedSubagentSession) =>
237
+ session.onUpdate((text) => {
238
+ onUpdate?.({
239
+ content: [{ type: "text", text }],
240
+ details: {
241
+ sandbox: "sandbox-runtime-outer",
242
+ session: session.info,
243
+ },
244
+ });
245
+ });
246
+ const sessionOptions = (
247
+ task: string,
248
+ parentId?: string,
249
+ ) => {
250
+ const approvalContext = {
251
+ broker: getBoundaryBroker(),
252
+ command: `process-backed subagent: ${task}`,
253
+ cwd: ctx.cwd,
254
+ sessionId: currentSessionId,
255
+ scopeKey: `${currentSessionId}:turn:${currentTurn}`,
256
+ agentName: parentId ? "nested-subagent" : "subagent",
257
+ humanApproval: humanApproval(ctx),
258
+ };
259
+ return {
260
+ task,
261
+ parentId,
262
+ cwd: ctx.cwd,
263
+ model: params.model,
264
+ tools: pi.getActiveTools().filter((name) => name !== "subagent"),
265
+ sandbox: options.sandbox,
266
+ review: async (trap: Parameters<typeof approveSandboxTrap>[0]) =>
267
+ (await approveSandboxTrap(trap, approvalContext)).action,
268
+ reviewDomain: async (
269
+ endpoint: Parameters<typeof approveDomainEndpoint>[0],
270
+ ) => (await approveDomainEndpoint(endpoint, approvalContext)).action,
271
+ };
272
+ };
273
+
274
+ if (action === "status") {
275
+ const sessions = params.sessionId
276
+ ? [requireSession().info]
277
+ : subagents.list();
278
+ return {
279
+ content: [
280
+ {
281
+ type: "text",
282
+ text:
283
+ sessions.length > 0
284
+ ? JSON.stringify(sessions, null, 2)
285
+ : "(no subagent sessions)",
286
+ },
287
+ ],
288
+ details: { sandbox: "sandbox-runtime-outer", sessions },
289
+ };
290
+ }
291
+
292
+ if (action === "stop") {
293
+ const session = requireSession();
294
+ await subagents.remove(session.id);
295
+ return {
296
+ content: [{ type: "text", text: `Stopped subagent ${session.id}` }],
297
+ details: { sandbox: "sandbox-runtime-outer", session: session.info },
298
+ };
299
+ }
300
+
301
+ if (action === "wait") {
302
+ const session = requireSession();
303
+ const unsubscribe = update(session);
304
+ try {
305
+ const result = await session.waitForSettled(undefined, signal);
306
+ return {
307
+ content: [
308
+ {
309
+ type: "text",
310
+ text:
311
+ result.text || "(subagent completed without assistant text)",
312
+ },
313
+ ],
314
+ details: { sandbox: "sandbox-runtime-outer", session: session.info },
315
+ };
316
+ } catch (error) {
317
+ if (signal?.aborted) {
318
+ await session.abort().catch(() => undefined);
319
+ }
320
+ throw error;
321
+ } finally {
322
+ unsubscribe();
323
+ }
324
+ }
325
+
326
+ if (action === "follow_up") {
327
+ const session = requireSession();
328
+ const unsubscribe = update(session);
329
+ try {
330
+ const target = await session.followUp(requireTask());
331
+ if (params.background) {
332
+ return {
333
+ content: [
334
+ {
335
+ type: "text",
336
+ text: `Follow-up queued for subagent ${session.id}`,
337
+ },
338
+ ],
339
+ details: { sandbox: "sandbox-runtime-outer", session: session.info },
340
+ };
341
+ }
342
+ const result = await session.waitForSettled(target, signal);
343
+ return {
344
+ content: [
345
+ {
346
+ type: "text",
347
+ text:
348
+ result.text || "(subagent completed without assistant text)",
349
+ },
350
+ ],
351
+ details: { sandbox: "sandbox-runtime-outer", session: session.info },
352
+ };
353
+ } catch (error) {
354
+ if (signal?.aborted) {
355
+ await session.abort().catch(() => undefined);
356
+ }
357
+ throw error;
358
+ } finally {
359
+ unsubscribe();
360
+ }
361
+ }
362
+
363
+ const task =
364
+ action === "handoff"
365
+ ? `Handoff from subagent session ${requireSession().id}.\n\nPrevious result:\n${requireSession().info.text}\n\nNext task:\n${requireTask()}`
366
+ : requireTask();
367
+ const parentId =
368
+ action === "handoff" ? requireSession().id : undefined;
369
+ const session = await subagents.start(sessionOptions(task, parentId));
370
+ const unsubscribe = update(session);
371
+ try {
372
+ const target = await session.prompt(task);
373
+ if (params.background) {
374
+ return {
375
+ content: [
376
+ {
377
+ type: "text",
378
+ text: `Started background subagent ${session.id}`,
379
+ },
380
+ ],
381
+ details: { sandbox: "sandbox-runtime-outer", session: session.info },
382
+ };
383
+ }
384
+ const result = await session.waitForSettled(target, signal);
385
+ if (action === "start") await subagents.remove(session.id);
386
+ return {
387
+ content: [
388
+ {
389
+ type: "text",
390
+ text: result.text || "(subagent completed without assistant text)",
391
+ },
392
+ ],
393
+ details: {
394
+ sandbox: "sandbox-runtime-outer",
395
+ session: session.info,
396
+ exitCode: result.exitCode,
397
+ },
398
+ };
399
+ } catch (error) {
400
+ if (!params.background) {
401
+ await subagents.remove(session.id).catch(() => undefined);
402
+ }
403
+ throw error;
404
+ } finally {
405
+ unsubscribe();
406
+ }
407
+ },
408
+ });
409
+
410
+ pi.on("user_bash", (_event, ctx) => ({
411
+ operations: sandboxOperations(ctx, () => currentTurn, options.sandbox),
412
+ }));
413
+
414
+ pi.on("turn_start", (event) => {
415
+ currentTurn = event.turnIndex;
416
+ });
417
+
418
+ pi.on("session_start", (_event, ctx) => {
419
+ if (ctx.hasUI) {
420
+ if (subagentProvider === "pi-subagents") {
421
+ const diagnostic = externalSubagentDiagnostic(pi);
422
+ ctx.ui.notify(diagnostic.message, diagnostic.level);
423
+ } else if (subagentProvider === "builtin") {
424
+ ctx.ui.notify(
425
+ "pi-sandbox subagent provider: builtin; worker process trees use the outer Sandbox Runtime sandbox",
426
+ "info",
427
+ );
428
+ } else {
429
+ ctx.ui.notify(
430
+ "pi-sandbox subagent provider: off; pi-sandbox protects Bash execution only",
431
+ "info",
432
+ );
433
+ }
434
+ }
435
+ if (process.platform !== "linux" && process.platform !== "darwin") {
436
+ if (ctx.hasUI) {
437
+ ctx.ui.notify(
438
+ `${EXTENSION_NAME} is unavailable on ${process.platform}; Bash commands will fail closed`,
439
+ "warning",
440
+ );
441
+ }
442
+ return;
443
+ }
444
+ if (ctx.hasUI) {
445
+ ctx.ui.notify(
446
+ process.platform === "darwin"
447
+ ? `${EXTENSION_NAME} enabled: macOS Sandbox Runtime with static filesystem policy and per-connection network review`
448
+ : `${EXTENSION_NAME} enabled: Linux Sandbox Runtime with static filesystem policy and per-connection network review`,
449
+ "info",
450
+ );
451
+ }
452
+ });
453
+
454
+ pi.on("session_shutdown", async () => {
455
+ await subagents?.shutdown();
456
+ });
457
+ }
458
+
459
+ export function registerPiSandbox(
460
+ pi: ExtensionAPI,
461
+ options: PiSandboxExtensionOptions = {},
462
+ ): Promise<void> {
463
+ const existing = registrations.get(pi);
464
+ if (existing) return existing;
465
+ const registration = performRegistration(pi, options);
466
+ registrations.set(pi, registration);
467
+ return registration;
468
+ }
469
+
470
+ export default async function piSandbox(pi: ExtensionAPI): Promise<void> {
471
+ await registerPiSandbox(pi);
472
+ }
@@ -0,0 +1,10 @@
1
+ export type HostnameResolver = (
2
+ hostname: string,
3
+ ) => Promise<Array<{ address: string; family: number }>>;
4
+
5
+ export function isPublicAddress(address: string, family?: number): boolean;
6
+ export function normalizePublicHostname(value: string): string | undefined;
7
+ export function validatePublicHostname(
8
+ value: string,
9
+ resolve?: HostnameResolver,
10
+ ): Promise<string | undefined>;
@@ -0,0 +1,72 @@
1
+ import { lookup } from "node:dns/promises";
2
+ import { BlockList, isIP } from "node:net";
3
+
4
+ const prohibitedAddresses = new BlockList();
5
+ for (const [network, prefix] of [
6
+ ["0.0.0.0", 8],
7
+ ["10.0.0.0", 8],
8
+ ["100.64.0.0", 10],
9
+ ["127.0.0.0", 8],
10
+ ["169.254.0.0", 16],
11
+ ["172.16.0.0", 12],
12
+ ["192.0.0.0", 24],
13
+ ["192.168.0.0", 16],
14
+ ["198.18.0.0", 15],
15
+ ["224.0.0.0", 4],
16
+ ["240.0.0.0", 4],
17
+ ]) {
18
+ prohibitedAddresses.addSubnet(network, prefix, "ipv4");
19
+ }
20
+ for (const [network, prefix] of [
21
+ ["::", 128],
22
+ ["::1", 128],
23
+ ["fc00::", 7],
24
+ ["fe80::", 10],
25
+ ["ff00::", 8],
26
+ ]) {
27
+ prohibitedAddresses.addSubnet(network, prefix, "ipv6");
28
+ }
29
+
30
+ export function isPublicAddress(address, family = isIP(address)) {
31
+ if (family === 4) return !prohibitedAddresses.check(address, "ipv4");
32
+ if (family === 6) return !prohibitedAddresses.check(address, "ipv6");
33
+ return false;
34
+ }
35
+
36
+ export function normalizePublicHostname(value) {
37
+ const hostname = value.trim().replace(/\.$/, "").toLowerCase();
38
+ if (
39
+ !hostname ||
40
+ hostname.length > 253 ||
41
+ isIP(hostname) !== 0 ||
42
+ !hostname.includes(".") ||
43
+ hostname.split(".").some(
44
+ (label) =>
45
+ !label ||
46
+ label.length > 63 ||
47
+ !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(label),
48
+ )
49
+ ) {
50
+ return undefined;
51
+ }
52
+ return hostname;
53
+ }
54
+
55
+ export async function validatePublicHostname(
56
+ value,
57
+ resolve = (hostname) => lookup(hostname, { all: true, verbatim: true }),
58
+ ) {
59
+ const hostname = normalizePublicHostname(value);
60
+ if (!hostname) return undefined;
61
+ try {
62
+ const addresses = await resolve(hostname);
63
+ return addresses.length > 0 &&
64
+ addresses.every(({ address, family }) =>
65
+ isPublicAddress(address, family),
66
+ )
67
+ ? hostname
68
+ : undefined;
69
+ } catch {
70
+ return undefined;
71
+ }
72
+ }
package/src/policy.ts ADDED
@@ -0,0 +1,102 @@
1
+ import { homedir } from "node:os";
2
+ import {
3
+ dirname,
4
+ isAbsolute,
5
+ join,
6
+ parse,
7
+ relative,
8
+ resolve,
9
+ } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import type { SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
12
+
13
+ const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
14
+ const NODE_INSTALL_ROOT = dirname(dirname(process.execPath));
15
+ const SANDBOX_RUNTIME_ROOT = dirname(
16
+ dirname(
17
+ fileURLToPath(import.meta.resolve("@anthropic-ai/sandbox-runtime")),
18
+ ),
19
+ );
20
+
21
+ export type SandboxPolicy = {
22
+ filesystem: {
23
+ denyRead: string[];
24
+ allowRead: string[];
25
+ allowWrite: string[];
26
+ denyWrite: string[];
27
+ };
28
+ network: {
29
+ allowedDomains: string[];
30
+ deniedDomains: string[];
31
+ allowLocalBinding: boolean;
32
+ allowAllUnixSockets: boolean;
33
+ allowUnixSockets: string[];
34
+ httpProxyPort?: number;
35
+ };
36
+ };
37
+
38
+ export function createDefaultPolicy(cwd: string): SandboxPolicy {
39
+ const workspace = resolve(cwd);
40
+ const home = resolve(homedir());
41
+ const denyRead = home === parse(home).root ? [] : [home];
42
+ const packageRelative = relative(workspace, PACKAGE_ROOT);
43
+ const packageIsInWorkspace =
44
+ packageRelative === "" ||
45
+ (!packageRelative.startsWith("..") && !isAbsolute(packageRelative));
46
+ return {
47
+ filesystem: {
48
+ denyRead,
49
+ allowRead: [
50
+ workspace,
51
+ NODE_INSTALL_ROOT,
52
+ SANDBOX_RUNTIME_ROOT,
53
+ join(home, ".gitconfig"),
54
+ join(home, ".config", "git", "config"),
55
+ "/dev/null",
56
+ ],
57
+ allowWrite: [workspace, "/dev/null"],
58
+ denyWrite: [
59
+ join(workspace, ".pi", "settings.json"),
60
+ join(workspace, ".pi", "sandbox.json"),
61
+ join(workspace, ".pi", "pi-auto-review.json"),
62
+ join(home, ".pi", "agent", "settings.json"),
63
+ join(home, ".pi", "agent", "permissions.json"),
64
+ join(home, ".pi", "agent", "pi-sandbox.json"),
65
+ join(home, ".pi", "agent", "sandbox.json"),
66
+ join(home, ".pi", "agent", "logs"),
67
+ ...(packageIsInWorkspace ? [] : [PACKAGE_ROOT]),
68
+ dirname(process.execPath),
69
+ ],
70
+ },
71
+ network: {
72
+ allowedDomains: [],
73
+ deniedDomains: [],
74
+ allowLocalBinding: false,
75
+ allowAllUnixSockets: false,
76
+ allowUnixSockets: [],
77
+ },
78
+ };
79
+ }
80
+
81
+ export function toSandboxRuntimeConfig(
82
+ policy: SandboxPolicy,
83
+ ): SandboxRuntimeConfig {
84
+ return {
85
+ filesystem: {
86
+ denyRead: [...policy.filesystem.denyRead],
87
+ allowRead: [...policy.filesystem.allowRead],
88
+ allowWrite: [...policy.filesystem.allowWrite],
89
+ denyWrite: [...policy.filesystem.denyWrite],
90
+ // pi-sandbox historically allowed commands such as `git remote set-url`.
91
+ // Sandbox Runtime continues to protect hooks independently.
92
+ allowGitConfig: true,
93
+ },
94
+ network: {
95
+ allowedDomains: [...policy.network.allowedDomains],
96
+ deniedDomains: [...policy.network.deniedDomains],
97
+ allowLocalBinding: policy.network.allowLocalBinding,
98
+ allowAllUnixSockets: policy.network.allowAllUnixSockets,
99
+ allowUnixSockets: [...policy.network.allowUnixSockets],
100
+ },
101
+ };
102
+ }