@trim21/personal-pi-extensions 0.0.426 → 0.0.428
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/README.md +2 -2
- package/package.json +1 -1
- package/src/bwrap/core.ts +23 -35
- package/src/bwrap/runtime.ts +2 -2
- package/src/claude-code/files.ts +13 -3
- package/src/bwrap/seccomp-aarch64.bpf +0 -0
- package/src/bwrap/seccomp-x86_64.bpf +0 -0
package/README.md
CHANGED
|
@@ -80,8 +80,8 @@ bash 工具(opencode 风格 `bash`、Claude Code 风格 `Bash`)注册了 `da
|
|
|
80
80
|
"writablePaths": [".", "/tmp", "~/my-projects"],
|
|
81
81
|
// 额外可写路径,与默认值合并(ro-bind)
|
|
82
82
|
"extraWritablePaths": ["~/.config"],
|
|
83
|
-
// tmpfs
|
|
84
|
-
"
|
|
83
|
+
// 沙箱内隐藏的路径:以 / 结尾的视为目录(挂空 tmpfs),否则按文件处理(--ro-bind-try /dev/null)
|
|
84
|
+
"denyPaths": [],
|
|
85
85
|
// 额外 bwrap 参数
|
|
86
86
|
"extraArgs": ["--die-with-parent"],
|
|
87
87
|
// 全权限执行的自动审批规则:命中规则的命令不弹确认框
|
package/package.json
CHANGED
package/src/bwrap/core.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { constants, type Dirent, existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { access as fsAccess, readdir, stat } from "node:fs/promises";
|
|
4
4
|
import { delimiter, join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
5
|
|
|
7
6
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
8
7
|
import { type BashOperations, getAgentDir, getShellConfig } from "@earendil-works/pi-coding-agent";
|
|
@@ -31,7 +30,12 @@ const bwrapConfigProperties = {
|
|
|
31
30
|
bwrapPath: Type.Optional(Type.String()),
|
|
32
31
|
writablePaths: Type.Array(Type.String()),
|
|
33
32
|
extraWritablePaths: Type.Array(Type.String()),
|
|
34
|
-
|
|
33
|
+
denyPaths: Type.Array(
|
|
34
|
+
Type.String({
|
|
35
|
+
description:
|
|
36
|
+
"沙箱内隐藏的路径:以 / 结尾为目录(挂空 tmpfs),否则为文件(--ro-bind-try /dev/null)",
|
|
37
|
+
}),
|
|
38
|
+
),
|
|
35
39
|
extraArgs: Type.Array(Type.String()),
|
|
36
40
|
networkAllowlist: Type.Array(
|
|
37
41
|
Type.String({ description: "允许直连的域名 / IP / CIDR,可带 :port" }),
|
|
@@ -71,7 +75,8 @@ export interface ResolvedBwrap {
|
|
|
71
75
|
bwrapPath?: string;
|
|
72
76
|
writablePaths: string[];
|
|
73
77
|
extraWritablePaths: string[];
|
|
74
|
-
|
|
78
|
+
/** 沙箱内隐藏的路径:以 / 结尾为目录(挂空 tmpfs),否则为文件(--ro-bind-try /dev/null)。 */
|
|
79
|
+
denyPaths: string[];
|
|
75
80
|
extraArgs: string[];
|
|
76
81
|
/** 允许直连的域名 / IP / IP:port 白名单(非空 = 启用 sing-box 网络过滤)。 */
|
|
77
82
|
networkAllowlist: string[];
|
|
@@ -85,7 +90,7 @@ const DEFAULT_CONFIG: BwrapConfig = {
|
|
|
85
90
|
mode: "workspace-write",
|
|
86
91
|
writablePaths: [".", "/tmp"],
|
|
87
92
|
extraWritablePaths: [],
|
|
88
|
-
|
|
93
|
+
denyPaths: [],
|
|
89
94
|
extraArgs: [],
|
|
90
95
|
networkAllowlist: [],
|
|
91
96
|
};
|
|
@@ -96,7 +101,7 @@ export function resolveBwrap(config: BwrapConfig): ResolvedBwrap {
|
|
|
96
101
|
bwrapPath: config.bwrapPath,
|
|
97
102
|
writablePaths: config.writablePaths ?? [".", "/tmp"],
|
|
98
103
|
extraWritablePaths: config.extraWritablePaths,
|
|
99
|
-
|
|
104
|
+
denyPaths: config.denyPaths ?? [],
|
|
100
105
|
extraArgs: config.extraArgs ?? [],
|
|
101
106
|
networkAllowlist: config.networkAllowlist ?? [],
|
|
102
107
|
singBoxPath: config.singBoxPath,
|
|
@@ -128,7 +133,7 @@ export function resolveHeadlessBwrap(config: BwrapConfig): ResolvedBwrap {
|
|
|
128
133
|
mode: "readonly",
|
|
129
134
|
writablePaths: [],
|
|
130
135
|
extraWritablePaths: [],
|
|
131
|
-
|
|
136
|
+
denyPaths: [],
|
|
132
137
|
extraArgs: [],
|
|
133
138
|
});
|
|
134
139
|
}
|
|
@@ -139,7 +144,7 @@ function deepMerge(base: BwrapConfig, overrides: Partial<BwrapConfig>): BwrapCon
|
|
|
139
144
|
bwrapPath: overrides.bwrapPath ?? base.bwrapPath,
|
|
140
145
|
writablePaths: overrides.writablePaths ?? base.writablePaths,
|
|
141
146
|
extraWritablePaths: [...base.extraWritablePaths, ...(overrides.extraWritablePaths ?? [])],
|
|
142
|
-
|
|
147
|
+
denyPaths: overrides.denyPaths ?? base.denyPaths,
|
|
143
148
|
extraArgs: overrides.extraArgs ?? base.extraArgs,
|
|
144
149
|
networkAllowlist: overrides.networkAllowlist ?? base.networkAllowlist,
|
|
145
150
|
singBoxPath: overrides.singBoxPath ?? base.singBoxPath,
|
|
@@ -323,8 +328,14 @@ export async function buildBwrapArgs(resolved: ResolvedBwrap, cwd: string): Prom
|
|
|
323
328
|
const absolutePath = resolveBwrapPath(path, cwd);
|
|
324
329
|
args.push("--bind-try", absolutePath, absolutePath);
|
|
325
330
|
}
|
|
326
|
-
|
|
327
|
-
|
|
331
|
+
// denyPaths:以 / 结尾的条目视为目录(挂空 tmpfs),否则视为文件(--ro-bind-try /dev/null 覆盖)
|
|
332
|
+
for (const path of resolved.denyPaths) {
|
|
333
|
+
const target = resolveBwrapPath(path, cwd);
|
|
334
|
+
if (path.endsWith("/")) {
|
|
335
|
+
args.push("--tmpfs", target);
|
|
336
|
+
} else {
|
|
337
|
+
args.push("--ro-bind-try", "/dev/null", target);
|
|
338
|
+
}
|
|
328
339
|
}
|
|
329
340
|
if (!resolved.network) args.push("--unshare-net");
|
|
330
341
|
// --ro-bind-try:目录不存在(或已被删除)时自动忽略
|
|
@@ -350,22 +361,6 @@ export async function buildBwrapArgs(resolved: ResolvedBwrap, cwd: string): Prom
|
|
|
350
361
|
return args;
|
|
351
362
|
}
|
|
352
363
|
|
|
353
|
-
const SECCOMP_BPF_FILE = (() => {
|
|
354
|
-
const directory = fileURLToPath(new URL(".", import.meta.url));
|
|
355
|
-
if (process.arch === "x64") return join(directory, "seccomp-x86_64.bpf");
|
|
356
|
-
if (process.arch === "arm64") return join(directory, "seccomp-aarch64.bpf");
|
|
357
|
-
return "";
|
|
358
|
-
})();
|
|
359
|
-
|
|
360
|
-
function getSeccompFd(): number | undefined {
|
|
361
|
-
if (!SECCOMP_BPF_FILE) return undefined;
|
|
362
|
-
try {
|
|
363
|
-
return openSync(SECCOMP_BPF_FILE, "r");
|
|
364
|
-
} catch {
|
|
365
|
-
return undefined;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
364
|
function killChild(child: ChildProcess): void {
|
|
370
365
|
if (!child.pid) return;
|
|
371
366
|
try {
|
|
@@ -448,19 +443,13 @@ export function createBwrapBashOperations(
|
|
|
448
443
|
});
|
|
449
444
|
}
|
|
450
445
|
|
|
451
|
-
const seccompFd = resolved.network ? undefined : getSeccompFd();
|
|
452
446
|
const child = spawn(
|
|
453
447
|
findBwrap(resolved.bwrapPath),
|
|
454
|
-
|
|
455
|
-
? [...baseArgs, "--", shell, "-lc", command]
|
|
456
|
-
: [...baseArgs, "--seccomp", "3", "--", shell, "-lc", command],
|
|
448
|
+
[...baseArgs, "--", shell, "-lc", command],
|
|
457
449
|
{
|
|
458
450
|
cwd,
|
|
459
451
|
detached: true,
|
|
460
|
-
stdio:
|
|
461
|
-
seccompFd === undefined
|
|
462
|
-
? ["ignore", "pipe", "pipe"]
|
|
463
|
-
: ["ignore", "pipe", "pipe", seccompFd],
|
|
452
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
464
453
|
env,
|
|
465
454
|
},
|
|
466
455
|
);
|
|
@@ -481,7 +470,6 @@ export function createBwrapBashOperations(
|
|
|
481
470
|
child.once("close", (exitCode) => {
|
|
482
471
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
483
472
|
signal?.removeEventListener("abort", onAbort);
|
|
484
|
-
if (seccompFd !== undefined) closeSync(seccompFd);
|
|
485
473
|
if (signal?.aborted) reject(new Error("aborted"));
|
|
486
474
|
else if (timedOut) reject(new Error(`timeout:${timeout}`));
|
|
487
475
|
else resolve({ exitCode });
|
package/src/bwrap/runtime.ts
CHANGED
|
@@ -708,9 +708,9 @@ export class BwrapRuntime {
|
|
|
708
708
|
const writable = runtime.writablePaths.map((path) =>
|
|
709
709
|
resolveBwrapPath(path, commandCtx.cwd),
|
|
710
710
|
);
|
|
711
|
-
const
|
|
711
|
+
const deny = runtime.denyPaths.map((path) => resolveBwrapPath(path, commandCtx.cwd));
|
|
712
712
|
commandCtx.ui.notify(
|
|
713
|
-
`bwrap ${runtime.mode} ${runtime.network ? "net" : "no-net"} write:[${writable.join(", ")}]
|
|
713
|
+
`bwrap ${runtime.mode} ${runtime.network ? "net" : "no-net"} write:[${writable.join(", ")}] deny:[${deny.join(", ") || "-"}]`,
|
|
714
714
|
"info",
|
|
715
715
|
);
|
|
716
716
|
}),
|
package/src/claude-code/files.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { Type } from "typebox";
|
|
|
17
17
|
import { appendLspDiagnosticText } from "../lib/lsp/diagnostic.js";
|
|
18
18
|
import { type LspService, registerLsp } from "../lib/lsp/lsp.js";
|
|
19
19
|
import { formatSubtitlePath } from "../lib/path.js";
|
|
20
|
+
import type { ToolPendant } from "../lib/pendant.ts";
|
|
20
21
|
import { guardWriteAccess } from "../lib/write-guard.js";
|
|
21
22
|
import {
|
|
22
23
|
type ClaudeCodeState,
|
|
@@ -302,7 +303,10 @@ export function registerFileTools(
|
|
|
302
303
|
content,
|
|
303
304
|
details: {
|
|
304
305
|
reads: { [key]: snapshot },
|
|
305
|
-
pendant: {
|
|
306
|
+
pendant: {
|
|
307
|
+
subtitle: formatSubtitlePath(ctx.cwd, filePath),
|
|
308
|
+
title: "Read",
|
|
309
|
+
} satisfies ToolPendant,
|
|
306
310
|
},
|
|
307
311
|
};
|
|
308
312
|
}
|
|
@@ -354,7 +358,10 @@ export function registerFileTools(
|
|
|
354
358
|
content: [{ type: "text", text: formatted.text }],
|
|
355
359
|
details: {
|
|
356
360
|
reads: { [key]: snapshot },
|
|
357
|
-
pendant: {
|
|
361
|
+
pendant: {
|
|
362
|
+
subtitle: formatSubtitlePath(ctx.cwd, filePath),
|
|
363
|
+
title: "Read",
|
|
364
|
+
} satisfies ToolPendant,
|
|
358
365
|
},
|
|
359
366
|
};
|
|
360
367
|
},
|
|
@@ -566,7 +573,10 @@ export function registerFileTools(
|
|
|
566
573
|
content: [{ type: "text" as const, text }],
|
|
567
574
|
details: {
|
|
568
575
|
...details,
|
|
569
|
-
pendant: {
|
|
576
|
+
pendant: {
|
|
577
|
+
subtitle: formatSubtitlePath(ctx.cwd, filePath, errorCount, warningCount),
|
|
578
|
+
title: "Edit",
|
|
579
|
+
} satisfies ToolPendant,
|
|
570
580
|
},
|
|
571
581
|
};
|
|
572
582
|
},
|
|
Binary file
|
|
Binary file
|