@letrquan/book 0.3.0 → 0.4.0-beta.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/CHANGELOG.md +1024 -0
- package/README.md +6 -3
- package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
- package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
- package/dist/app-J7BVPSUH.js.map +1 -0
- package/dist/chunk-32WBCUAN.js +660 -0
- package/dist/chunk-32WBCUAN.js.map +1 -0
- package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
- package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
- package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
- package/dist/chunk-AHC7OU75.js.map +1 -0
- package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
- package/dist/chunk-AJZWLKLQ.js.map +1 -0
- package/dist/chunk-B5FC3MYZ.js +395 -0
- package/dist/chunk-B5FC3MYZ.js.map +1 -0
- package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
- package/dist/chunk-DNFQTSMJ.js.map +1 -0
- package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
- package/dist/chunk-E5WLQSKA.js.map +1 -0
- package/dist/chunk-ECLC4CIJ.js +1181 -0
- package/dist/chunk-ECLC4CIJ.js.map +1 -0
- package/dist/chunk-EEX6QNXV.js +127 -0
- package/dist/chunk-EEX6QNXV.js.map +1 -0
- package/dist/chunk-EFXVSQ5J.js +931 -0
- package/dist/chunk-EFXVSQ5J.js.map +1 -0
- package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
- package/dist/chunk-ENLB7NYV.js.map +1 -0
- package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
- package/dist/chunk-FPR7SKIE.js.map +1 -0
- package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
- package/dist/chunk-HFB74CLM.js.map +1 -0
- package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
- package/dist/chunk-HOZXKOE3.js.map +1 -0
- package/dist/chunk-IDYWN37T.js +36 -0
- package/dist/chunk-IDYWN37T.js.map +1 -0
- package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
- package/dist/chunk-JGF5HLSI.js.map +1 -0
- package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
- package/dist/chunk-KDNWMKXV.js.map +1 -0
- package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
- package/dist/chunk-MEOB2DZA.js.map +1 -0
- package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
- package/dist/chunk-N2G4Q7ME.js +2672 -0
- package/dist/chunk-N2G4Q7ME.js.map +1 -0
- package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
- package/dist/chunk-OJBMDM5H.js.map +1 -0
- package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
- package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
- package/dist/chunk-REASC2QG.js.map +1 -0
- package/dist/chunk-SBTU5BEC.js +114 -0
- package/dist/chunk-SBTU5BEC.js.map +1 -0
- package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
- package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
- package/dist/chunk-UCFJ43OS.js.map +1 -0
- package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
- package/dist/chunk-W7SLYS3M.js.map +1 -0
- package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
- package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
- package/dist/chunk-XJRJVXJR.js.map +1 -0
- package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
- package/dist/chunk-YIL6HTJX.js.map +1 -0
- package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
- package/dist/context-C3RAP6K5.js +41 -0
- package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
- package/dist/index.js +657 -754
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
- package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
- package/dist/job-runner.js +10 -3
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.js +13 -4
- package/dist/job-supervisor.js.map +1 -1
- package/dist/loader-DQNPDKOX.js +27 -0
- package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
- package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
- package/dist/memory-store-I6Z54VUN.js +51 -0
- package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
- package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
- package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
- package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
- package/dist/provider-approvals-5AGH3RVT.js +76 -0
- package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
- package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
- package/dist/scrollback-GD76GNM4.js.map +1 -0
- package/dist/sdk.d.ts +876 -64
- package/dist/sdk.js +57 -37
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-KDBH45AQ.js +43 -0
- package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
- package/dist/shell-selection-Z5WORZU4.js +31 -0
- package/dist/shell-selection-Z5WORZU4.js.map +1 -0
- package/package.json +5 -5
- package/dist/app-GE4OHJOC.js.map +0 -1
- package/dist/chunk-224FJZLH.js.map +0 -1
- package/dist/chunk-5RDRAO4B.js.map +0 -1
- package/dist/chunk-5YHTKWJN.js.map +0 -1
- package/dist/chunk-62QL5QUP.js.map +0 -1
- package/dist/chunk-67UZEWWF.js.map +0 -1
- package/dist/chunk-6OQQOMZR.js +0 -785
- package/dist/chunk-6OQQOMZR.js.map +0 -1
- package/dist/chunk-AQEQX4FF.js.map +0 -1
- package/dist/chunk-C7ADUNPR.js +0 -273
- package/dist/chunk-C7ADUNPR.js.map +0 -1
- package/dist/chunk-EFJ2NH6E.js.map +0 -1
- package/dist/chunk-IME7R2UI.js.map +0 -1
- package/dist/chunk-J62VZYOB.js +0 -1339
- package/dist/chunk-J62VZYOB.js.map +0 -1
- package/dist/chunk-JTPOI3KU.js.map +0 -1
- package/dist/chunk-QYBD22H5.js.map +0 -1
- package/dist/chunk-RFJ6Q6LE.js.map +0 -1
- package/dist/chunk-S4JMID4G.js +0 -259
- package/dist/chunk-S4JMID4G.js.map +0 -1
- package/dist/chunk-TLX3H2CL.js.map +0 -1
- package/dist/chunk-VPNLCMVR.js.map +0 -1
- package/dist/chunk-W3XFHEF4.js.map +0 -1
- package/dist/chunk-X4NJT436.js.map +0 -1
- package/dist/chunk-X66I2NQO.js +0 -137
- package/dist/chunk-X66I2NQO.js.map +0 -1
- package/dist/chunk-Y2VHN7TC.js.map +0 -1
- package/dist/chunk-YC6W4EJH.js.map +0 -1
- package/dist/context-CBOBDY5Q.js +0 -38
- package/dist/loader-S77OVTHH.js +0 -18
- package/dist/memory-store-DZUT5HUH.js +0 -75
- package/dist/scrollback-35ME2GFX.js.map +0 -1
- package/dist/settings-loader-JUNPGAAM.js +0 -35
- package/dist/shell-selection-CDHGJ4GC.js +0 -17
- /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
- /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
- /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
- /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
- /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
- /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
- /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
- /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
- /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
- /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
- /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
- /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
- /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
- /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
- /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
- /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
- /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
- /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
- /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
- /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
- /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
- /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
updateWorkspaceTrust
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HFB74CLM.js";
|
|
4
4
|
import {
|
|
5
5
|
HOOK_EVENTS
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-REASC2QG.js";
|
|
7
7
|
|
|
8
8
|
// src/hook-approvals.ts
|
|
9
9
|
import { createHash } from "crypto";
|
|
@@ -81,4 +81,4 @@ export {
|
|
|
81
81
|
describeDeclaredHook,
|
|
82
82
|
persistProjectHookChoice
|
|
83
83
|
};
|
|
84
|
-
//# sourceMappingURL=chunk-
|
|
84
|
+
//# sourceMappingURL=chunk-WV3O5PGX.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDebugLogger
|
|
3
|
+
} from "./chunk-OJBMDM5H.js";
|
|
1
4
|
import {
|
|
2
5
|
isOutside,
|
|
3
6
|
resolveWorkspacePath
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
createDebugLogger
|
|
7
|
-
} from "./chunk-62QL5QUP.js";
|
|
7
|
+
} from "./chunk-YIL6HTJX.js";
|
|
8
8
|
|
|
9
9
|
// src/sandbox.ts
|
|
10
10
|
import {
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
realpathSync,
|
|
21
21
|
statSync
|
|
22
22
|
} from "fs";
|
|
23
|
-
import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
23
|
+
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
24
24
|
import { homedir, platform } from "os";
|
|
25
25
|
|
|
26
26
|
// src/tools/glob-regex.ts
|
|
@@ -28,8 +28,8 @@ import { posix, win32 } from "path";
|
|
|
28
28
|
import picomatch from "picomatch";
|
|
29
29
|
import { convertPathToPattern } from "tinyglobby";
|
|
30
30
|
function globToRegex(pattern) {
|
|
31
|
-
const
|
|
32
|
-
return new RegExp("^" +
|
|
31
|
+
const source = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*|\*|\?/g, (wildcard) => wildcard === "?" ? "." : ".*");
|
|
32
|
+
return new RegExp("^" + source + "$");
|
|
33
33
|
}
|
|
34
34
|
var log = createDebugLogger("tools:glob");
|
|
35
35
|
var MAX_GLOB_PATTERN_LENGTH = 1e4;
|
|
@@ -332,9 +332,11 @@ function unsandboxedRefusalMessage(reason) {
|
|
|
332
332
|
].join(" ");
|
|
333
333
|
}
|
|
334
334
|
function sandboxPolicySummary(settings, state) {
|
|
335
|
+
const gitDirectories = settings.filesystem.allowGitWrites ? "writable; hooks, config and pointer files read-only (sandbox.filesystem.allowGitWrites=true)" : "read-only (sandbox.filesystem.allowGitWrites=false)";
|
|
335
336
|
return {
|
|
336
337
|
unsandboxedCommands: settings.allowUnsandboxedCommands ? "allowed (sandbox.allowUnsandboxedCommands=true)" : "refused (sandbox.allowUnsandboxedCommands=false)",
|
|
337
|
-
autoAllowBash: !settings.autoAllowBashIfSandboxed ? "off \u2014 every Bash call goes through the normal permission path (sandbox.autoAllowBashIfSandboxed=false)" : !state.sandboxActive ? "inert \u2014 no command is sandboxed here, so nothing is auto-allowed (sandbox.autoAllowBashIfSandboxed=true)" : state.adjudicationConfigured ? "inert \u2014 permissions.deny/ask rules are configured, so every Bash call still goes through the normal permission path (sandbox.autoAllowBashIfSandboxed=true)" : "on for genuinely sandboxed commands (sandbox.autoAllowBashIfSandboxed=true)"
|
|
338
|
+
autoAllowBash: !settings.autoAllowBashIfSandboxed ? "off \u2014 every Bash call goes through the normal permission path (sandbox.autoAllowBashIfSandboxed=false)" : !state.sandboxActive ? "inert \u2014 no command is sandboxed here, so nothing is auto-allowed (sandbox.autoAllowBashIfSandboxed=true)" : state.adjudicationConfigured ? "inert \u2014 permissions.deny/ask rules are configured, so every Bash call still goes through the normal permission path (sandbox.autoAllowBashIfSandboxed=true)" : "on for genuinely sandboxed commands (sandbox.autoAllowBashIfSandboxed=true)",
|
|
339
|
+
gitDirectories
|
|
338
340
|
};
|
|
339
341
|
}
|
|
340
342
|
var MAX_CONTROL_FILE_BYTES = 4096;
|
|
@@ -352,7 +354,7 @@ function entryKind(path) {
|
|
|
352
354
|
}
|
|
353
355
|
}
|
|
354
356
|
var realSandboxHost = {
|
|
355
|
-
path: { resolve, join, relative, isAbsolute, sep, dirname },
|
|
357
|
+
path: { resolve, join, relative, isAbsolute, sep, dirname, basename },
|
|
356
358
|
exists: existsSync,
|
|
357
359
|
// A path that vanished between the probe and the spawn is absent as far as the
|
|
358
360
|
// mount list is concerned. `statSync` throws for it, and a throw here would
|
|
@@ -448,31 +450,76 @@ function canonicalPath(path, host) {
|
|
|
448
450
|
function insideWorkspace(candidate, workspaceRoot, host) {
|
|
449
451
|
return !isOutside(canonicalPath(workspaceRoot, host), canonicalPath(candidate, host), host.path);
|
|
450
452
|
}
|
|
453
|
+
function workspaceComponents(path, workspaceRoot, host) {
|
|
454
|
+
const root = host.path.resolve(workspaceRoot);
|
|
455
|
+
const resolved = host.path.resolve(path);
|
|
456
|
+
const rel = host.path.relative(root, resolved);
|
|
457
|
+
if (isOutside(root, resolved, host.path) || !rel || rel === ".." || rel.startsWith(".." + host.path.sep))
|
|
458
|
+
return [];
|
|
459
|
+
const segments = rel.split(host.path.sep).filter(Boolean);
|
|
460
|
+
const components = [];
|
|
461
|
+
let current = root;
|
|
462
|
+
for (const segment of segments) {
|
|
463
|
+
current = host.path.join(current, segment);
|
|
464
|
+
components.push(current);
|
|
465
|
+
}
|
|
466
|
+
return components;
|
|
467
|
+
}
|
|
468
|
+
function looksLikeGitDir(dir, host) {
|
|
469
|
+
if (!host.isDirectory(dir)) return false;
|
|
470
|
+
const head = host.path.join(dir, "HEAD");
|
|
471
|
+
if (!host.exists(head)) return false;
|
|
472
|
+
const objects = host.path.join(dir, "objects");
|
|
473
|
+
if (host.isDirectory(objects)) return true;
|
|
474
|
+
const commondir = host.path.join(dir, "commondir");
|
|
475
|
+
if (host.exists(commondir)) return true;
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
451
478
|
function workspaceGitDirs(workspaceRoot, host) {
|
|
452
479
|
const root = host.path.resolve(workspaceRoot);
|
|
480
|
+
const canonicalRoot = canonicalPath(root, host);
|
|
453
481
|
const found = [];
|
|
482
|
+
const pointerPaths = [];
|
|
483
|
+
const refusals = [];
|
|
484
|
+
const dotGit = host.path.join(root, ".git");
|
|
454
485
|
const add = (candidate, workTree) => {
|
|
455
486
|
const dir = host.path.resolve(candidate);
|
|
456
487
|
if (found.some((entry) => entry.dir === dir)) return;
|
|
457
488
|
if (!host.isDirectory(dir) || !insideWorkspace(dir, root, host)) return;
|
|
489
|
+
if (dir !== dotGit && !looksLikeGitDir(dir, host)) return;
|
|
490
|
+
if (canonicalPath(dir, host) === canonicalRoot) {
|
|
491
|
+
if (!refusals.some((r) => r.path === dir)) {
|
|
492
|
+
refusals.push({ path: dir, reason: "workspace-root-is-git-dir" });
|
|
493
|
+
}
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
458
496
|
found.push({ dir, workTree: host.path.resolve(workTree) });
|
|
459
497
|
};
|
|
460
|
-
|
|
461
|
-
if (!host.exists(dotGit)) return found;
|
|
498
|
+
if (!host.exists(dotGit)) return { gitDirs: found, pointerPaths, refusals };
|
|
462
499
|
if (host.isDirectory(dotGit)) {
|
|
463
500
|
add(dotGit, root);
|
|
464
501
|
} else {
|
|
465
502
|
const target = /^gitdir:\s*(.+)$/m.exec(host.readFile(dotGit) ?? "")?.[1]?.trim();
|
|
466
|
-
if (!target) return found;
|
|
467
|
-
|
|
503
|
+
if (!target) return { gitDirs: found, pointerPaths, refusals };
|
|
504
|
+
const resolvedTarget = host.path.isAbsolute(target) ? target : host.path.join(root, target);
|
|
505
|
+
pointerPaths.push(resolvedTarget);
|
|
506
|
+
add(resolvedTarget, root);
|
|
468
507
|
}
|
|
469
508
|
for (let index = 0; index < found.length; index++) {
|
|
470
509
|
const { dir, workTree } = found[index];
|
|
471
510
|
const common = host.readFile(host.path.join(dir, "commondir"))?.trim();
|
|
472
|
-
if (common)
|
|
511
|
+
if (common) {
|
|
512
|
+
const resolvedCommon = host.path.isAbsolute(common) ? common : host.path.join(dir, common);
|
|
513
|
+
pointerPaths.push(resolvedCommon);
|
|
514
|
+
add(resolvedCommon, workTree);
|
|
515
|
+
}
|
|
473
516
|
for (const name of host.readDir(host.path.join(dir, "worktrees"))) {
|
|
474
517
|
const linked = host.path.join(dir, "worktrees", name);
|
|
475
518
|
const workTreeGitdir = host.readFile(host.path.join(linked, "gitdir"))?.trim();
|
|
519
|
+
if (workTreeGitdir) {
|
|
520
|
+
const resolvedGitdir = host.path.isAbsolute(workTreeGitdir) ? workTreeGitdir : host.path.join(linked, workTreeGitdir);
|
|
521
|
+
pointerPaths.push(resolvedGitdir);
|
|
522
|
+
}
|
|
476
523
|
add(linked, workTreeGitdir ? host.path.dirname(workTreeGitdir) : root);
|
|
477
524
|
}
|
|
478
525
|
const addModules = (modulesRoot, prefix, depth) => {
|
|
@@ -494,9 +541,11 @@ function workspaceGitDirs(workspaceRoot, host) {
|
|
|
494
541
|
if (host.isDirectory(pointer)) continue;
|
|
495
542
|
const target = /^gitdir:\s*(.+)$/m.exec(host.readFile(pointer) ?? "")?.[1]?.trim();
|
|
496
543
|
if (!target) continue;
|
|
497
|
-
|
|
544
|
+
const resolvedTarget = host.path.isAbsolute(target) ? target : host.path.join(workTree, target);
|
|
545
|
+
pointerPaths.push(resolvedTarget);
|
|
546
|
+
add(resolvedTarget, workTree);
|
|
498
547
|
}
|
|
499
|
-
return found;
|
|
548
|
+
return { gitDirs: found, pointerPaths, refusals };
|
|
500
549
|
}
|
|
501
550
|
function parseConfigValue(raw) {
|
|
502
551
|
const text = raw.trim();
|
|
@@ -569,12 +618,23 @@ function describeControlPathRefusal(refusal) {
|
|
|
569
618
|
if (refusal.reason === "oversized-git-config") {
|
|
570
619
|
return `Refused to run this command in the sandbox: ${refusal.path} is larger than the ${MAX_GIT_CONFIG_BYTES / 1024 / 1024} MiB read limit for a git config file, so the core.hooksPath it may declare cannot be found and the hooks directory it names cannot be made read-only. Trim the config, or run this command without the sandbox.`;
|
|
571
620
|
}
|
|
621
|
+
if (refusal.reason === "workspace-root-is-git-dir") {
|
|
622
|
+
return `Refused to run this command in the sandbox: ${refusal.path} is the workspace root and a git directory, so the sandbox cannot make it read-only without making the whole workspace read-only. Run this command without the sandbox.`;
|
|
623
|
+
}
|
|
572
624
|
return `Refused to run this command in the sandbox: ${refusal.path} is a symlink, and the sandbox cannot protect a symlinked control path. The link itself sits in the writable workspace, so a command could remove it, put a directory or file in its place, and write the settings, hooks or repository config the host then reads through it. Replace the symlink with the file or directory it names, or run this command without the sandbox.`;
|
|
573
625
|
}
|
|
574
|
-
function protectedWorkspacePaths(workspaceRoot, host = realSandboxHost) {
|
|
626
|
+
function protectedWorkspacePaths(workspaceRoot, host = realSandboxHost, options = {}) {
|
|
575
627
|
const root = host.path.resolve(workspaceRoot);
|
|
576
|
-
const
|
|
628
|
+
const canonicalRoot = canonicalPath(root, host);
|
|
629
|
+
const allowGitWrites = options.allowGitWrites ?? false;
|
|
630
|
+
const result = {
|
|
631
|
+
pinnedDirectories: [],
|
|
632
|
+
mounts: [],
|
|
633
|
+
refusals: [],
|
|
634
|
+
gitDirControlMounts: []
|
|
635
|
+
};
|
|
577
636
|
const seen = /* @__PURE__ */ new Set();
|
|
637
|
+
const safeDenyDirs = (options.denyDirectories ?? []).filter((d) => host.isDirectory(d)).map((d) => canonicalPath(d, host));
|
|
578
638
|
const target = (path, kind) => {
|
|
579
639
|
const resolved = host.path.resolve(path);
|
|
580
640
|
if (seen.has(resolved)) return null;
|
|
@@ -584,7 +644,12 @@ function protectedWorkspacePaths(workspaceRoot, host = realSandboxHost) {
|
|
|
584
644
|
};
|
|
585
645
|
const first = host.entryKind(resolved);
|
|
586
646
|
if (first === "symlink") {
|
|
587
|
-
|
|
647
|
+
const parentCanonical = canonicalPath(host.path.dirname(resolved), host);
|
|
648
|
+
const symlinkLivePath = host.path.join(parentCanonical, host.path.basename(resolved));
|
|
649
|
+
const isSafe = safeDenyDirs.some(
|
|
650
|
+
(dir) => !isOutside(dir, symlinkLivePath, host.path) && dir !== symlinkLivePath
|
|
651
|
+
);
|
|
652
|
+
if (!isSafe && !result.refusals.some((entry) => entry.path === resolved)) {
|
|
588
653
|
result.refusals.push({ path: resolved, reason: "symlinked-control-path" });
|
|
589
654
|
}
|
|
590
655
|
return null;
|
|
@@ -609,12 +674,34 @@ function protectedWorkspacePaths(workspaceRoot, host = realSandboxHost) {
|
|
|
609
674
|
const mount = target(path, "hidden-file");
|
|
610
675
|
if (mount) result.mounts.push(mount);
|
|
611
676
|
};
|
|
677
|
+
const { gitDirs, pointerPaths, refusals: gitDirRefusals } = workspaceGitDirs(root, host);
|
|
678
|
+
result.refusals.push(...gitDirRefusals);
|
|
679
|
+
const boundCanonical = [];
|
|
680
|
+
const gitDirMounts = /* @__PURE__ */ new Set();
|
|
681
|
+
if (!allowGitWrites) {
|
|
682
|
+
const canonicalDirs = [...new Set(gitDirs.map((gitDir) => canonicalPath(gitDir.dir, host)))];
|
|
683
|
+
canonicalDirs.sort(
|
|
684
|
+
(left, right) => left.split(host.path.sep).length - right.split(host.path.sep).length
|
|
685
|
+
);
|
|
686
|
+
for (const canonicalDir of canonicalDirs) {
|
|
687
|
+
if (boundCanonical.some((outer) => !isOutside(outer, canonicalDir, host.path))) continue;
|
|
688
|
+
if (!host.exists(canonicalDir)) continue;
|
|
689
|
+
boundCanonical.push(canonicalDir);
|
|
690
|
+
const rel = host.path.relative(canonicalRoot, canonicalDir);
|
|
691
|
+
const bindPath = rel ? host.path.resolve(root, rel) : root;
|
|
692
|
+
const mount = { path: bindPath, kind: "directory", present: true };
|
|
693
|
+
gitDirMounts.add(mount);
|
|
694
|
+
result.mounts.push(mount);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
612
697
|
addDirectory(host.path.join(root, ".book"));
|
|
613
698
|
addMaskedFile(host.path.join(root, ".book", "settings.local.json"));
|
|
614
699
|
addFile(host.path.join(root, ".bookrc.json"));
|
|
615
700
|
const dotGit = host.path.join(root, ".git");
|
|
616
701
|
if (host.entryKind(dotGit) === "directory") {
|
|
617
|
-
|
|
702
|
+
if (allowGitWrites) {
|
|
703
|
+
result.pinnedDirectories.push(dotGit);
|
|
704
|
+
}
|
|
618
705
|
} else {
|
|
619
706
|
addFile(dotGit);
|
|
620
707
|
}
|
|
@@ -641,7 +728,7 @@ function protectedWorkspacePaths(workspaceRoot, host = realSandboxHost) {
|
|
|
641
728
|
readConfig(target2, workTree, depth + 1);
|
|
642
729
|
}
|
|
643
730
|
};
|
|
644
|
-
for (const gitDir of
|
|
731
|
+
for (const gitDir of gitDirs) {
|
|
645
732
|
addDirectory(host.path.join(gitDir.dir, "hooks"));
|
|
646
733
|
for (const name of ["config", "config.worktree", "commondir", "gitdir"]) {
|
|
647
734
|
addFile(host.path.join(gitDir.dir, name));
|
|
@@ -652,6 +739,78 @@ function protectedWorkspacePaths(workspaceRoot, host = realSandboxHost) {
|
|
|
652
739
|
readConfig(host.path.join(gitDir.dir, name), gitDir.workTree, 0);
|
|
653
740
|
}
|
|
654
741
|
}
|
|
742
|
+
const presentReadOnlyDirs = result.mounts.filter((mount) => mount.kind === "directory" && mount.present).map((mount) => canonicalPath(mount.path, host));
|
|
743
|
+
const filteredMounts = [];
|
|
744
|
+
const gitDirControlMounts = [];
|
|
745
|
+
for (const mount of result.mounts) {
|
|
746
|
+
if (mount.kind === "hidden-file") {
|
|
747
|
+
filteredMounts.push(mount);
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const mountCanonical = canonicalPath(mount.path, host);
|
|
751
|
+
const strictlyInsidePresentReadOnlyDir = presentReadOnlyDirs.some(
|
|
752
|
+
(dir) => !isOutside(dir, mountCanonical, host.path) && dir !== mountCanonical
|
|
753
|
+
);
|
|
754
|
+
if (strictlyInsidePresentReadOnlyDir) {
|
|
755
|
+
if (!allowGitWrites) {
|
|
756
|
+
const strictlyInsideBoundGitDir = boundCanonical.some(
|
|
757
|
+
(dir) => !isOutside(dir, mountCanonical, host.path) && dir !== mountCanonical
|
|
758
|
+
);
|
|
759
|
+
if (strictlyInsideBoundGitDir) {
|
|
760
|
+
gitDirControlMounts.push(mount);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
764
|
+
filteredMounts.push(mount);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
result.mounts = filteredMounts;
|
|
768
|
+
result.gitDirControlMounts = gitDirControlMounts;
|
|
769
|
+
if (!allowGitWrites) {
|
|
770
|
+
for (const mount of result.mounts) {
|
|
771
|
+
const mountCanonical = canonicalPath(mount.path, host);
|
|
772
|
+
if (isOutside(canonicalRoot, mountCanonical, host.path) || mountCanonical === canonicalRoot) {
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
const isInsidePresentReadOnly = presentReadOnlyDirs.some(
|
|
776
|
+
(dir) => !isOutside(dir, mountCanonical, host.path) && dir !== mountCanonical
|
|
777
|
+
);
|
|
778
|
+
if (isInsidePresentReadOnly) continue;
|
|
779
|
+
const ancestors = workspaceComponents(mount.path, root, host).slice(0, -1);
|
|
780
|
+
for (const anc of ancestors) {
|
|
781
|
+
if (!host.isDirectory(anc)) continue;
|
|
782
|
+
const ancCanonical = canonicalPath(anc, host);
|
|
783
|
+
if (presentReadOnlyDirs.some((dir) => !isOutside(dir, ancCanonical, host.path))) continue;
|
|
784
|
+
if (!result.pinnedDirectories.includes(anc)) {
|
|
785
|
+
result.pinnedDirectories.push(anc);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
const safeDirs = [...presentReadOnlyDirs, ...safeDenyDirs];
|
|
791
|
+
const candidatesToCheck = /* @__PURE__ */ new Set([
|
|
792
|
+
...gitDirs.map((g) => g.dir),
|
|
793
|
+
...pointerPaths,
|
|
794
|
+
...result.mounts.map((m) => m.path),
|
|
795
|
+
...result.gitDirControlMounts.map((m) => m.path)
|
|
796
|
+
]);
|
|
797
|
+
const checkedComponents = /* @__PURE__ */ new Set();
|
|
798
|
+
for (const cand of candidatesToCheck) {
|
|
799
|
+
for (const comp of workspaceComponents(cand, root, host)) {
|
|
800
|
+
if (checkedComponents.has(comp)) continue;
|
|
801
|
+
checkedComponents.add(comp);
|
|
802
|
+
if (host.entryKind(comp) === "symlink") {
|
|
803
|
+
const parentCanonical = canonicalPath(host.path.dirname(comp), host);
|
|
804
|
+
const symlinkLivePath = host.path.join(parentCanonical, host.path.basename(comp));
|
|
805
|
+
const isSafe = safeDirs.some(
|
|
806
|
+
(dir) => !isOutside(dir, symlinkLivePath, host.path) && dir !== symlinkLivePath
|
|
807
|
+
);
|
|
808
|
+
if (!isSafe && !result.refusals.some((r) => r.path === comp)) {
|
|
809
|
+
result.refusals.push({ path: comp, reason: "symlinked-control-path" });
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
655
814
|
return result;
|
|
656
815
|
}
|
|
657
816
|
function mountProtected(args, mount, host) {
|
|
@@ -670,7 +829,7 @@ function mountProtected(args, mount, host) {
|
|
|
670
829
|
}
|
|
671
830
|
args.push("--tmpfs", target, "--remount-ro", target);
|
|
672
831
|
}
|
|
673
|
-
function buildSandboxExecution(bwrapPath, command, workspaceRoot, settings, host = realSandboxHost) {
|
|
832
|
+
function buildSandboxExecution(bwrapPath, command, workspaceRoot, settings, host = realSandboxHost, protectedPaths) {
|
|
674
833
|
const args = [];
|
|
675
834
|
args.push("--unshare-pid", "--unshare-ipc", "--unshare-uts", "--die-with-parent");
|
|
676
835
|
args.push("--proc", "/proc");
|
|
@@ -680,26 +839,46 @@ function buildSandboxExecution(bwrapPath, command, workspaceRoot, settings, host
|
|
|
680
839
|
}
|
|
681
840
|
args.push("--tmpfs", "/tmp");
|
|
682
841
|
bindIfPresent(args, "--bind", workspaceRoot, host);
|
|
683
|
-
const
|
|
842
|
+
const paths = protectedPaths ?? protectedWorkspacePaths(workspaceRoot, host, {
|
|
843
|
+
allowGitWrites: settings.filesystem.allowGitWrites,
|
|
844
|
+
denyDirectories: [...settings.filesystem.denyWrite, ...settings.filesystem.denyRead].map(
|
|
845
|
+
(p) => expandPath(p, host)
|
|
846
|
+
)
|
|
847
|
+
});
|
|
684
848
|
const isProtectedArea = (path) => {
|
|
685
849
|
const canonical = canonicalPath(expandPath(path, host), host);
|
|
686
|
-
return
|
|
850
|
+
return paths.mounts.some(
|
|
687
851
|
(mount) => !isOutside(canonicalPath(mount.path, host), canonical, host.path)
|
|
688
852
|
);
|
|
689
853
|
};
|
|
690
|
-
for (const dir of
|
|
854
|
+
for (const dir of paths.pinnedDirectories) {
|
|
691
855
|
args.push("--bind", host.path.resolve(dir), host.path.resolve(dir));
|
|
692
856
|
}
|
|
693
857
|
for (const path of settings.filesystem.allowWrite) {
|
|
694
858
|
if (isProtectedArea(path)) continue;
|
|
695
859
|
bindIfPresent(args, "--bind", path, host);
|
|
696
860
|
}
|
|
697
|
-
for (const mount of
|
|
861
|
+
for (const mount of paths.mounts) mountProtected(args, mount, host);
|
|
862
|
+
const deferredOptInCanonicals = [];
|
|
698
863
|
for (const path of settings.filesystem.allowWrite) {
|
|
699
864
|
if (!isProtectedArea(path)) continue;
|
|
700
|
-
|
|
865
|
+
const target = expandPath(path, host);
|
|
866
|
+
if (host.exists(target)) {
|
|
867
|
+
args.push("--bind", target, target);
|
|
868
|
+
deferredOptInCanonicals.push(canonicalPath(target, host));
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
for (const mount of paths.gitDirControlMounts) {
|
|
872
|
+
const mountCanonical = canonicalPath(mount.path, host);
|
|
873
|
+
const strictlyBelowDeferredOptIn = deferredOptInCanonicals.some(
|
|
874
|
+
(optIn) => !isOutside(optIn, mountCanonical, host.path) && optIn !== mountCanonical
|
|
875
|
+
);
|
|
876
|
+
const explicitlyOptedIn = deferredOptInCanonicals.includes(mountCanonical);
|
|
877
|
+
if (strictlyBelowDeferredOptIn && !explicitlyOptedIn) {
|
|
878
|
+
mountProtected(args, mount, host);
|
|
879
|
+
}
|
|
701
880
|
}
|
|
702
|
-
for (const mount of
|
|
881
|
+
for (const mount of paths.mounts) {
|
|
703
882
|
if (mount.kind === "hidden-file") mountProtected(args, mount, host);
|
|
704
883
|
}
|
|
705
884
|
for (const path of settings.filesystem.denyWrite) bindIfPresent(args, "--ro-bind", path, host);
|
|
@@ -730,25 +909,62 @@ function decideSandboxExecution(ctx, command, workdir) {
|
|
|
730
909
|
} catch (error) {
|
|
731
910
|
return { sandboxed: false, error: error instanceof Error ? error.message : String(error) };
|
|
732
911
|
}
|
|
912
|
+
let protectedPaths;
|
|
733
913
|
if (sandbox) {
|
|
734
|
-
const
|
|
914
|
+
const denyDirectories = [
|
|
915
|
+
...ctx.sandbox.filesystem.denyWrite,
|
|
916
|
+
...ctx.sandbox.filesystem.denyRead
|
|
917
|
+
].map((p) => expandPath(p, realSandboxHost));
|
|
918
|
+
protectedPaths = protectedWorkspacePaths(ctx.workspaceRoot, realSandboxHost, {
|
|
919
|
+
allowGitWrites: ctx.sandbox.filesystem.allowGitWrites,
|
|
920
|
+
denyDirectories
|
|
921
|
+
});
|
|
922
|
+
const refusal = protectedPaths.refusals[0];
|
|
735
923
|
if (refusal) return { sandboxed: false, error: describeControlPathRefusal(refusal) };
|
|
736
924
|
}
|
|
737
|
-
const exec = sandbox?.wrap(command, ctx.workspaceRoot);
|
|
738
|
-
if (exec)
|
|
925
|
+
const exec = sandbox?.wrap(command, ctx.workspaceRoot, protectedPaths);
|
|
926
|
+
if (exec) {
|
|
927
|
+
return {
|
|
928
|
+
sandboxed: true,
|
|
929
|
+
exec,
|
|
930
|
+
gitDirReadOnly: !ctx.sandbox.filesystem.allowGitWrites
|
|
931
|
+
};
|
|
932
|
+
}
|
|
739
933
|
if (ctx.sandbox.failIfUnavailable) {
|
|
740
934
|
return { sandboxed: false, error: "Sandbox unavailable and failIfUnavailable is set" };
|
|
741
935
|
}
|
|
742
936
|
return unsandboxed("unavailable");
|
|
743
937
|
}
|
|
744
|
-
function withGitConfigReadOnlyNotice(text, output, sandboxed, command = "") {
|
|
745
|
-
if (!sandboxed
|
|
746
|
-
|
|
938
|
+
function withGitConfigReadOnlyNotice(text, output, sandboxed, command = "", gitDirReadOnly) {
|
|
939
|
+
if (!sandboxed) return text;
|
|
940
|
+
const isConfigFileFailure = /could not (?:write|lock) config file/.test(output);
|
|
941
|
+
if (isConfigFileFailure) {
|
|
942
|
+
if (writesAGlobalConfig(command)) return text;
|
|
943
|
+
return gitDirReadOnly ? withReadOnlyGitDirNote(text) : withLegacyGitConfigNote(text);
|
|
944
|
+
}
|
|
945
|
+
if (gitDirReadOnly) {
|
|
946
|
+
const linesWithRo = output.split("\n").filter((line) => /\bRead-only file system\b/i.test(line));
|
|
947
|
+
const runsGit = /(?:^|[;&|(\s])git\b/.test(command);
|
|
948
|
+
const hasDotGitSegment = linesWithRo.some(
|
|
949
|
+
(line) => /(?:^|[/\s'"`])\.git(?=[/\\s'":`]|[\r\n]|$)/.test(line)
|
|
950
|
+
);
|
|
951
|
+
const roFailure = linesWithRo.length > 0 && (runsGit || hasDotGitSegment);
|
|
952
|
+
if (roFailure) {
|
|
953
|
+
return withReadOnlyGitDirNote(text);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return text;
|
|
957
|
+
}
|
|
958
|
+
function withLegacyGitConfigNote(text) {
|
|
747
959
|
return `${text}
|
|
748
960
|
Note: .git/config is read-only inside the sandbox, so git cannot update repository configuration there. Run this command outside the sandbox.`;
|
|
749
961
|
}
|
|
962
|
+
function withReadOnlyGitDirNote(text) {
|
|
963
|
+
return `${text}
|
|
964
|
+
Note: the repository's git directory is read-only inside the sandbox (sandbox.filesystem.allowGitWrites is off), so git cannot write to it here. Do not retry the command or change sandbox settings yourself; tell the user it has to run outside the sandbox: they can run it, or allow it in their own ~/.book/settings.json, where sandbox.excludedCommands runs matching commands unsandboxed while sandbox.allowUnsandboxedCommands is true and sandbox.filesystem.allowGitWrites lets sandboxed git write again; workspace settings cannot enable either.`;
|
|
965
|
+
}
|
|
750
966
|
function writesAGlobalConfig(command) {
|
|
751
|
-
return
|
|
967
|
+
return /(?:^|[;&|(\s])git\s+[^|;&]*\bconfig\b[^|;&]*\s--(?:global|system)\b/.test(command);
|
|
752
968
|
}
|
|
753
969
|
function createSandbox(settings) {
|
|
754
970
|
if (!settings.enabled) return null;
|
|
@@ -786,13 +1002,21 @@ function createSandbox(settings) {
|
|
|
786
1002
|
);
|
|
787
1003
|
}
|
|
788
1004
|
return {
|
|
789
|
-
wrap(command, workspaceRoot) {
|
|
790
|
-
return buildSandboxExecution(
|
|
1005
|
+
wrap(command, workspaceRoot, protectedPaths) {
|
|
1006
|
+
return buildSandboxExecution(
|
|
1007
|
+
bwrap,
|
|
1008
|
+
command,
|
|
1009
|
+
workspaceRoot,
|
|
1010
|
+
settings,
|
|
1011
|
+
realSandboxHost,
|
|
1012
|
+
protectedPaths
|
|
1013
|
+
);
|
|
791
1014
|
},
|
|
792
1015
|
describe() {
|
|
793
1016
|
return [
|
|
794
1017
|
`bubblewrap (${bwrap})`,
|
|
795
1018
|
hasDomainPolicy(settings) ? "network disabled" : "host network shared",
|
|
1019
|
+
settings.filesystem.allowGitWrites ? "git directories writable (hooks, config and pointer files read-only)" : "git directories read-only",
|
|
796
1020
|
`${settings.filesystem.allowWrite.length} extra writable path(s)`,
|
|
797
1021
|
`${settings.filesystem.denyWrite.length} read-only path(s)`,
|
|
798
1022
|
`${settings.filesystem.denyRead.length} masked path(s)`,
|
|
@@ -823,4 +1047,4 @@ export {
|
|
|
823
1047
|
withGitConfigReadOnlyNotice,
|
|
824
1048
|
createSandbox
|
|
825
1049
|
};
|
|
826
|
-
//# sourceMappingURL=chunk-
|
|
1050
|
+
//# sourceMappingURL=chunk-XJRJVXJR.js.map
|