@trim21/personal-pi-extensions 0.1.523 → 0.1.524
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/package.json +1 -1
- package/src/bwrap/core.ts +22 -7
package/package.json
CHANGED
package/src/bwrap/core.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
2
|
import { constants, type Dirent, existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { access as fsAccess, readdir, stat } from "node:fs/promises";
|
|
3
|
+
import { access as fsAccess, readdir, realpath, stat } from "node:fs/promises";
|
|
4
4
|
import { delimiter, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
@@ -337,20 +337,34 @@ export async function findGitDirs(root: string): Promise<string[]> {
|
|
|
337
337
|
return found;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
/**
|
|
341
|
+
* bwrap 创建挂载点时不跟随目标路径中的 symlink 组件(防 symlink 逃逸),
|
|
342
|
+
* 含绝对 symlink 的配置路径会以 "Can't mkdir parents ... No such file or directory" 失败。
|
|
343
|
+
* 已存在的路径先解析成真实路径(沙箱内 ro-bind 的 / 下同样可见,symlink 语义不变);
|
|
344
|
+
* 不存在的路径保持原样,交给 --*-try 的跳过语义处理。
|
|
345
|
+
*/
|
|
346
|
+
async function realpathOrSelf(path: string): Promise<string> {
|
|
347
|
+
try {
|
|
348
|
+
return await realpath(path);
|
|
349
|
+
} catch {
|
|
350
|
+
return path;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
340
354
|
export async function buildBwrapArgs(resolved: ResolvedBwrap, cwd: string): Promise<string[]> {
|
|
341
355
|
const args = ["--new-session", "--die-with-parent", "--unshare-user", "--unshare-pid"];
|
|
342
356
|
// --*-bind-try:配置的路径不存在时忽略该项而不是让整条命令失败
|
|
343
357
|
for (const path of resolved.writablePaths) {
|
|
344
|
-
const absolutePath = resolveBwrapPath(path, cwd);
|
|
358
|
+
const absolutePath = await realpathOrSelf(resolveBwrapPath(path, cwd));
|
|
345
359
|
args.push("--bind-try", absolutePath, absolutePath);
|
|
346
360
|
}
|
|
347
361
|
for (const path of resolved.extraWritablePaths) {
|
|
348
|
-
const absolutePath = resolveBwrapPath(path, cwd);
|
|
362
|
+
const absolutePath = await realpathOrSelf(resolveBwrapPath(path, cwd));
|
|
349
363
|
args.push("--bind-try", absolutePath, absolutePath);
|
|
350
364
|
}
|
|
351
365
|
// denyPaths:以 / 结尾的条目视为目录(挂空 tmpfs),否则视为文件(--ro-bind-try /dev/null 覆盖)
|
|
352
366
|
for (const path of resolved.denyPaths) {
|
|
353
|
-
const target = resolveBwrapPath(path, cwd);
|
|
367
|
+
const target = await realpathOrSelf(resolveBwrapPath(path, cwd));
|
|
354
368
|
if (path.endsWith("/")) {
|
|
355
369
|
args.push("--tmpfs", target);
|
|
356
370
|
} else {
|
|
@@ -360,13 +374,13 @@ export async function buildBwrapArgs(resolved: ResolvedBwrap, cwd: string): Prom
|
|
|
360
374
|
if (!resolved.network) args.push("--unshare-net");
|
|
361
375
|
// --ro-bind-try:目录不存在(或已被删除)时自动忽略
|
|
362
376
|
for (const name of PROTECTED_DIRS) {
|
|
363
|
-
const absolutePath = join(cwd, name);
|
|
377
|
+
const absolutePath = await realpathOrSelf(join(cwd, name));
|
|
364
378
|
args.push("--ro-bind-try", absolutePath, absolutePath);
|
|
365
379
|
}
|
|
366
380
|
// 工作区下所有 .git 一律只读:可写 bind 之上的覆盖绑定,防止命令篡改仓库元数据。
|
|
367
381
|
// 根目录本身是 git 仓库时只保护根 .git(递归扫描有成本,绝大多数情况根即唯一仓库);
|
|
368
382
|
// 根不是 git 仓库时才递归扫描嵌套仓库(如 monorepo 子仓库)。
|
|
369
|
-
const rootGit = join(cwd, ".git");
|
|
383
|
+
const rootGit = await realpathOrSelf(join(cwd, ".git"));
|
|
370
384
|
let gitDirs: string[];
|
|
371
385
|
try {
|
|
372
386
|
await stat(rootGit);
|
|
@@ -375,7 +389,8 @@ export async function buildBwrapArgs(resolved: ResolvedBwrap, cwd: string): Prom
|
|
|
375
389
|
gitDirs = await findGitDirs(cwd);
|
|
376
390
|
}
|
|
377
391
|
for (const gitDir of gitDirs) {
|
|
378
|
-
|
|
392
|
+
const realGitDir = await realpathOrSelf(gitDir);
|
|
393
|
+
args.push("--ro-bind-try", realGitDir, realGitDir);
|
|
379
394
|
}
|
|
380
395
|
args.push(...resolved.extraArgs);
|
|
381
396
|
return args;
|