@cdoing/core 0.1.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/dist/agents/coordinator.d.ts +114 -0
- package/dist/agents/coordinator.d.ts.map +1 -0
- package/dist/agents/coordinator.js +158 -0
- package/dist/agents/coordinator.js.map +1 -0
- package/dist/context-providers/clipboard.d.ts +13 -0
- package/dist/context-providers/clipboard.d.ts.map +1 -0
- package/dist/context-providers/clipboard.js +53 -0
- package/dist/context-providers/clipboard.js.map +1 -0
- package/dist/context-providers/codebase.d.ts +46 -0
- package/dist/context-providers/codebase.d.ts.map +1 -0
- package/dist/context-providers/codebase.js +273 -0
- package/dist/context-providers/codebase.js.map +1 -0
- package/dist/context-providers/diff.d.ts +18 -0
- package/dist/context-providers/diff.d.ts.map +1 -0
- package/dist/context-providers/diff.js +63 -0
- package/dist/context-providers/diff.js.map +1 -0
- package/dist/context-providers/docs.d.ts +21 -0
- package/dist/context-providers/docs.d.ts.map +1 -0
- package/dist/context-providers/docs.js +180 -0
- package/dist/context-providers/docs.js.map +1 -0
- package/dist/context-providers/file-include.d.ts +13 -0
- package/dist/context-providers/file-include.d.ts.map +1 -0
- package/dist/context-providers/file-include.js +82 -0
- package/dist/context-providers/file-include.js.map +1 -0
- package/dist/context-providers/folder.d.ts +19 -0
- package/dist/context-providers/folder.d.ts.map +1 -0
- package/dist/context-providers/folder.js +130 -0
- package/dist/context-providers/folder.js.map +1 -0
- package/dist/context-providers/git.d.ts +19 -0
- package/dist/context-providers/git.d.ts.map +1 -0
- package/dist/context-providers/git.js +74 -0
- package/dist/context-providers/git.js.map +1 -0
- package/dist/context-providers/index.d.ts +26 -0
- package/dist/context-providers/index.d.ts.map +1 -0
- package/dist/context-providers/index.js +37 -0
- package/dist/context-providers/index.js.map +1 -0
- package/dist/context-providers/open-files.d.ts +25 -0
- package/dist/context-providers/open-files.d.ts.map +1 -0
- package/dist/context-providers/open-files.js +134 -0
- package/dist/context-providers/open-files.js.map +1 -0
- package/dist/context-providers/problems.d.ts +24 -0
- package/dist/context-providers/problems.d.ts.map +1 -0
- package/dist/context-providers/problems.js +97 -0
- package/dist/context-providers/problems.js.map +1 -0
- package/dist/context-providers/registry.d.ts +61 -0
- package/dist/context-providers/registry.d.ts.map +1 -0
- package/dist/context-providers/registry.js +92 -0
- package/dist/context-providers/registry.js.map +1 -0
- package/dist/context-providers/terminal.d.ts +25 -0
- package/dist/context-providers/terminal.d.ts.map +1 -0
- package/dist/context-providers/terminal.js +55 -0
- package/dist/context-providers/terminal.js.map +1 -0
- package/dist/context-providers/tree.d.ts +29 -0
- package/dist/context-providers/tree.d.ts.map +1 -0
- package/dist/context-providers/tree.js +172 -0
- package/dist/context-providers/tree.js.map +1 -0
- package/dist/context-providers/types.d.ts +72 -0
- package/dist/context-providers/types.d.ts.map +1 -0
- package/dist/context-providers/types.js +10 -0
- package/dist/context-providers/types.js.map +1 -0
- package/dist/context-providers/url.d.ts +27 -0
- package/dist/context-providers/url.d.ts.map +1 -0
- package/dist/context-providers/url.js +131 -0
- package/dist/context-providers/url.js.map +1 -0
- package/dist/effort/index.d.ts +78 -0
- package/dist/effort/index.d.ts.map +1 -0
- package/dist/effort/index.js +146 -0
- package/dist/effort/index.js.map +1 -0
- package/dist/hooks/index.d.ts +47 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +151 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +152 -0
- package/dist/index.js.map +1 -0
- package/dist/indexing/chunker.d.ts +25 -0
- package/dist/indexing/chunker.d.ts.map +1 -0
- package/dist/indexing/chunker.js +217 -0
- package/dist/indexing/chunker.js.map +1 -0
- package/dist/indexing/database.d.ts +49 -0
- package/dist/indexing/database.d.ts.map +1 -0
- package/dist/indexing/database.js +287 -0
- package/dist/indexing/database.js.map +1 -0
- package/dist/indexing/index.d.ts +9 -0
- package/dist/indexing/index.d.ts.map +1 -0
- package/dist/indexing/index.js +13 -0
- package/dist/indexing/index.js.map +1 -0
- package/dist/indexing/indexer.d.ts +63 -0
- package/dist/indexing/indexer.d.ts.map +1 -0
- package/dist/indexing/indexer.js +352 -0
- package/dist/indexing/indexer.js.map +1 -0
- package/dist/indexing/recent-edits-cache.d.ts +77 -0
- package/dist/indexing/recent-edits-cache.d.ts.map +1 -0
- package/dist/indexing/recent-edits-cache.js +123 -0
- package/dist/indexing/recent-edits-cache.js.map +1 -0
- package/dist/indexing/types.d.ts +39 -0
- package/dist/indexing/types.d.ts.map +1 -0
- package/dist/indexing/types.js +6 -0
- package/dist/indexing/types.js.map +1 -0
- package/dist/mcp/index.d.ts +33 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +37 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/manager.d.ts +123 -0
- package/dist/mcp/manager.d.ts.map +1 -0
- package/dist/mcp/manager.js +331 -0
- package/dist/mcp/manager.js.map +1 -0
- package/dist/oauth.d.ts +33 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +312 -0
- package/dist/oauth.js.map +1 -0
- package/dist/permissions/index.d.ts +216 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +938 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/plan/index.d.ts +20 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan/index.js +24 -0
- package/dist/plan/index.js.map +1 -0
- package/dist/plan/manager.d.ts +101 -0
- package/dist/plan/manager.d.ts.map +1 -0
- package/dist/plan/manager.js +170 -0
- package/dist/plan/manager.js.map +1 -0
- package/dist/rules/index.d.ts +28 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +31 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/manager.d.ts +77 -0
- package/dist/rules/manager.d.ts.map +1 -0
- package/dist/rules/manager.js +279 -0
- package/dist/rules/manager.js.map +1 -0
- package/dist/rules/types.d.ts +34 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +9 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/sandbox/filesystem.d.ts +20 -0
- package/dist/sandbox/filesystem.d.ts.map +1 -0
- package/dist/sandbox/filesystem.js +141 -0
- package/dist/sandbox/filesystem.js.map +1 -0
- package/dist/sandbox/index.d.ts +4 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +8 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/manager.d.ts +47 -0
- package/dist/sandbox/manager.d.ts.map +1 -0
- package/dist/sandbox/manager.js +220 -0
- package/dist/sandbox/manager.js.map +1 -0
- package/dist/sandbox/network.d.ts +14 -0
- package/dist/sandbox/network.d.ts.map +1 -0
- package/dist/sandbox/network.js +87 -0
- package/dist/sandbox/network.js.map +1 -0
- package/dist/sandbox/types.d.ts +42 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/sandbox/types.js +25 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/tools/ast-edit.d.ts +57 -0
- package/dist/tools/ast-edit.d.ts.map +1 -0
- package/dist/tools/ast-edit.js +443 -0
- package/dist/tools/ast-edit.js.map +1 -0
- package/dist/tools/code-verify.d.ts +8 -0
- package/dist/tools/code-verify.d.ts.map +1 -0
- package/dist/tools/code-verify.js +159 -0
- package/dist/tools/code-verify.js.map +1 -0
- package/dist/tools/codebase-search.d.ts +17 -0
- package/dist/tools/codebase-search.d.ts.map +1 -0
- package/dist/tools/codebase-search.js +104 -0
- package/dist/tools/codebase-search.js.map +1 -0
- package/dist/tools/file-delete.d.ts +26 -0
- package/dist/tools/file-delete.d.ts.map +1 -0
- package/dist/tools/file-delete.js +179 -0
- package/dist/tools/file-delete.js.map +1 -0
- package/dist/tools/file-edit.d.ts +10 -0
- package/dist/tools/file-edit.d.ts.map +1 -0
- package/dist/tools/file-edit.js +138 -0
- package/dist/tools/file-edit.js.map +1 -0
- package/dist/tools/file-read.d.ts +12 -0
- package/dist/tools/file-read.d.ts.map +1 -0
- package/dist/tools/file-read.js +211 -0
- package/dist/tools/file-read.js.map +1 -0
- package/dist/tools/file-run.d.ts +10 -0
- package/dist/tools/file-run.d.ts.map +1 -0
- package/dist/tools/file-run.js +179 -0
- package/dist/tools/file-run.js.map +1 -0
- package/dist/tools/file-write.d.ts +10 -0
- package/dist/tools/file-write.d.ts.map +1 -0
- package/dist/tools/file-write.js +134 -0
- package/dist/tools/file-write.js.map +1 -0
- package/dist/tools/glob-search.d.ts +8 -0
- package/dist/tools/glob-search.d.ts.map +1 -0
- package/dist/tools/glob-search.js +108 -0
- package/dist/tools/glob-search.js.map +1 -0
- package/dist/tools/grep-search.d.ts +8 -0
- package/dist/tools/grep-search.d.ts.map +1 -0
- package/dist/tools/grep-search.js +139 -0
- package/dist/tools/grep-search.js.map +1 -0
- package/dist/tools/list-dir.d.ts +16 -0
- package/dist/tools/list-dir.d.ts.map +1 -0
- package/dist/tools/list-dir.js +183 -0
- package/dist/tools/list-dir.js.map +1 -0
- package/dist/tools/multi-edit.d.ts +16 -0
- package/dist/tools/multi-edit.d.ts.map +1 -0
- package/dist/tools/multi-edit.js +163 -0
- package/dist/tools/multi-edit.js.map +1 -0
- package/dist/tools/notebook-edit.d.ts +31 -0
- package/dist/tools/notebook-edit.d.ts.map +1 -0
- package/dist/tools/notebook-edit.js +321 -0
- package/dist/tools/notebook-edit.js.map +1 -0
- package/dist/tools/registry.d.ts +16 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +41 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/shell-exec.d.ts +12 -0
- package/dist/tools/shell-exec.d.ts.map +1 -0
- package/dist/tools/shell-exec.js +261 -0
- package/dist/tools/shell-exec.js.map +1 -0
- package/dist/tools/sub-agent-manager.d.ts +57 -0
- package/dist/tools/sub-agent-manager.d.ts.map +1 -0
- package/dist/tools/sub-agent-manager.js +153 -0
- package/dist/tools/sub-agent-manager.js.map +1 -0
- package/dist/tools/sub-agent-status.d.ts +12 -0
- package/dist/tools/sub-agent-status.d.ts.map +1 -0
- package/dist/tools/sub-agent-status.js +59 -0
- package/dist/tools/sub-agent-status.js.map +1 -0
- package/dist/tools/sub-agent-terminate.d.ts +12 -0
- package/dist/tools/sub-agent-terminate.d.ts.map +1 -0
- package/dist/tools/sub-agent-terminate.js +55 -0
- package/dist/tools/sub-agent-terminate.js.map +1 -0
- package/dist/tools/sub-agent.d.ts +34 -0
- package/dist/tools/sub-agent.d.ts.map +1 -0
- package/dist/tools/sub-agent.js +140 -0
- package/dist/tools/sub-agent.js.map +1 -0
- package/dist/tools/system-info.d.ts +24 -0
- package/dist/tools/system-info.d.ts.map +1 -0
- package/dist/tools/system-info.js +220 -0
- package/dist/tools/system-info.js.map +1 -0
- package/dist/tools/todo.d.ts +16 -0
- package/dist/tools/todo.d.ts.map +1 -0
- package/dist/tools/todo.js +144 -0
- package/dist/tools/todo.js.map +1 -0
- package/dist/tools/types.d.ts +20 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +3 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/view-diff.d.ts +11 -0
- package/dist/tools/view-diff.d.ts.map +1 -0
- package/dist/tools/view-diff.js +88 -0
- package/dist/tools/view-diff.js.map +1 -0
- package/dist/tools/view-repo-map.d.ts +18 -0
- package/dist/tools/view-repo-map.d.ts.map +1 -0
- package/dist/tools/view-repo-map.js +245 -0
- package/dist/tools/view-repo-map.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +13 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +106 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/tools/web-search.d.ts +10 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +106 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/utils/gitignore.d.ts +10 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +104 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/lazy-apply.d.ts +45 -0
- package/dist/utils/lazy-apply.d.ts.map +1 -0
- package/dist/utils/lazy-apply.js +164 -0
- package/dist/utils/lazy-apply.js.map +1 -0
- package/dist/utils/memory.d.ts +36 -0
- package/dist/utils/memory.d.ts.map +1 -0
- package/dist/utils/memory.js +136 -0
- package/dist/utils/memory.js.map +1 -0
- package/dist/utils/path-matching.d.ts +24 -0
- package/dist/utils/path-matching.d.ts.map +1 -0
- package/dist/utils/path-matching.js +116 -0
- package/dist/utils/path-matching.js.map +1 -0
- package/dist/utils/path-safety.d.ts +13 -0
- package/dist/utils/path-safety.d.ts.map +1 -0
- package/dist/utils/path-safety.js +54 -0
- package/dist/utils/path-safety.js.map +1 -0
- package/dist/utils/project-config.d.ts +18 -0
- package/dist/utils/project-config.d.ts.map +1 -0
- package/dist/utils/project-config.js +76 -0
- package/dist/utils/project-config.js.map +1 -0
- package/dist/utils/search-match.d.ts +63 -0
- package/dist/utils/search-match.d.ts.map +1 -0
- package/dist/utils/search-match.js +426 -0
- package/dist/utils/search-match.js.map +1 -0
- package/dist/utils/shell-paths.d.ts +17 -0
- package/dist/utils/shell-paths.d.ts.map +1 -0
- package/dist/utils/shell-paths.js +107 -0
- package/dist/utils/shell-paths.js.map +1 -0
- package/dist/utils/streaming-diff.d.ts +45 -0
- package/dist/utils/streaming-diff.d.ts.map +1 -0
- package/dist/utils/streaming-diff.js +230 -0
- package/dist/utils/streaming-diff.js.map +1 -0
- package/dist/utils/todo.d.ts +47 -0
- package/dist/utils/todo.d.ts.map +1 -0
- package/dist/utils/todo.js +102 -0
- package/dist/utils/todo.js.map +1 -0
- package/package.json +23 -0
- package/src/agents/coordinator.ts +240 -0
- package/src/context-providers/clipboard.ts +48 -0
- package/src/context-providers/codebase.ts +274 -0
- package/src/context-providers/diff.ts +66 -0
- package/src/context-providers/docs.ts +160 -0
- package/src/context-providers/file-include.ts +54 -0
- package/src/context-providers/folder.ts +106 -0
- package/src/context-providers/git.ts +72 -0
- package/src/context-providers/index.ts +26 -0
- package/src/context-providers/open-files.ts +113 -0
- package/src/context-providers/problems.ts +100 -0
- package/src/context-providers/registry.ts +99 -0
- package/src/context-providers/terminal.ts +58 -0
- package/src/context-providers/tree.ts +161 -0
- package/src/context-providers/types.ts +84 -0
- package/src/context-providers/url.ts +138 -0
- package/src/effort/index.ts +177 -0
- package/src/hooks/index.ts +148 -0
- package/src/index.ts +114 -0
- package/src/indexing/README.md +267 -0
- package/src/indexing/chunker.ts +206 -0
- package/src/indexing/database.ts +299 -0
- package/src/indexing/index.ts +15 -0
- package/src/indexing/indexer.ts +383 -0
- package/src/indexing/recent-edits-cache.ts +150 -0
- package/src/indexing/types.ts +44 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/manager.ts +385 -0
- package/src/oauth.ts +330 -0
- package/src/permissions/index.ts +1011 -0
- package/src/plan/index.ts +20 -0
- package/src/plan/manager.ts +233 -0
- package/src/rules/index.ts +28 -0
- package/src/rules/manager.ts +276 -0
- package/src/rules/types.ts +40 -0
- package/src/sandbox/filesystem.ts +135 -0
- package/src/sandbox/index.ts +9 -0
- package/src/sandbox/manager.ts +213 -0
- package/src/sandbox/network.ts +101 -0
- package/src/sandbox/types.ts +63 -0
- package/src/tools/ast-edit.ts +493 -0
- package/src/tools/code-verify.ts +143 -0
- package/src/tools/codebase-search.ts +117 -0
- package/src/tools/file-delete.ts +155 -0
- package/src/tools/file-edit.ts +115 -0
- package/src/tools/file-read.ts +195 -0
- package/src/tools/file-run.ts +158 -0
- package/src/tools/file-write.ts +104 -0
- package/src/tools/glob-search.ts +80 -0
- package/src/tools/grep-search.ts +120 -0
- package/src/tools/list-dir.ts +172 -0
- package/src/tools/multi-edit.ts +138 -0
- package/src/tools/notebook-edit.ts +342 -0
- package/src/tools/registry.ts +43 -0
- package/src/tools/shell-exec.ts +251 -0
- package/src/tools/sub-agent-manager.ts +183 -0
- package/src/tools/sub-agent-status.ts +67 -0
- package/src/tools/sub-agent-terminate.ts +62 -0
- package/src/tools/sub-agent.ts +162 -0
- package/src/tools/system-info.ts +248 -0
- package/src/tools/todo.ts +149 -0
- package/src/tools/types.ts +21 -0
- package/src/tools/view-diff.ts +99 -0
- package/src/tools/view-repo-map.ts +249 -0
- package/src/tools/web-fetch.ts +118 -0
- package/src/tools/web-search.ts +129 -0
- package/src/utils/gitignore.ts +73 -0
- package/src/utils/lazy-apply.ts +189 -0
- package/src/utils/memory.ts +124 -0
- package/src/utils/path-matching.ts +84 -0
- package/src/utils/path-safety.ts +19 -0
- package/src/utils/project-config.ts +41 -0
- package/src/utils/search-match.ts +495 -0
- package/src/utils/shell-paths.ts +79 -0
- package/src/utils/streaming-diff.ts +260 -0
- package/src/utils/todo.ts +115 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Filesystem — checks read/write access against sandbox rules.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { matchPath } from "../utils/path-matching";
|
|
7
|
+
import type { SandboxConfig, SandboxCheckResult } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check whether reading a file is allowed by sandbox rules.
|
|
11
|
+
* Only blocks paths listed in denyRead.
|
|
12
|
+
*/
|
|
13
|
+
export function checkReadAccess(
|
|
14
|
+
filePath: string,
|
|
15
|
+
config: SandboxConfig,
|
|
16
|
+
workingDir: string,
|
|
17
|
+
projectDir: string,
|
|
18
|
+
): SandboxCheckResult {
|
|
19
|
+
if (!config.enabled) return { allowed: true };
|
|
20
|
+
|
|
21
|
+
const resolved = path.resolve(filePath);
|
|
22
|
+
|
|
23
|
+
for (const deny of config.filesystem.denyRead) {
|
|
24
|
+
if (matchPath(resolved, deny, projectDir, workingDir)) {
|
|
25
|
+
return { allowed: false, reason: `Sandbox: read access denied for ${resolved} (matches denyRead rule "${deny}")` };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { allowed: true };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check whether writing to a file is allowed by sandbox rules.
|
|
34
|
+
* DenyWrite takes priority, then path must be within workingDir or allowWrite paths.
|
|
35
|
+
*/
|
|
36
|
+
export function checkWriteAccess(
|
|
37
|
+
filePath: string,
|
|
38
|
+
config: SandboxConfig,
|
|
39
|
+
workingDir: string,
|
|
40
|
+
projectDir: string,
|
|
41
|
+
): SandboxCheckResult {
|
|
42
|
+
if (!config.enabled) return { allowed: true };
|
|
43
|
+
|
|
44
|
+
const resolved = path.resolve(filePath);
|
|
45
|
+
const normalizedWorkingDir = path.resolve(workingDir);
|
|
46
|
+
|
|
47
|
+
// Check denyWrite first (deny always wins)
|
|
48
|
+
for (const deny of config.filesystem.denyWrite) {
|
|
49
|
+
if (matchPath(resolved, deny, projectDir, workingDir)) {
|
|
50
|
+
return { allowed: false, reason: `Sandbox: write access denied for ${resolved} (matches denyWrite rule "${deny}")` };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Allow writes within workingDir
|
|
55
|
+
if (resolved === normalizedWorkingDir || resolved.startsWith(normalizedWorkingDir + path.sep)) {
|
|
56
|
+
return { allowed: true };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check allowWrite paths
|
|
60
|
+
for (const allow of config.filesystem.allowWrite) {
|
|
61
|
+
if (matchPath(resolved, allow, projectDir, workingDir)) {
|
|
62
|
+
return { allowed: true };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { allowed: false, reason: `Sandbox: write access denied for ${resolved} (outside working directory and not in allowWrite)` };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Best-effort heuristic: parse a shell command to detect read/write targets.
|
|
71
|
+
* Returns denied if any detected target violates sandbox rules.
|
|
72
|
+
*/
|
|
73
|
+
export function checkShellCommandPaths(
|
|
74
|
+
command: string,
|
|
75
|
+
config: SandboxConfig,
|
|
76
|
+
workingDir: string,
|
|
77
|
+
projectDir: string,
|
|
78
|
+
): SandboxCheckResult {
|
|
79
|
+
if (!config.enabled) return { allowed: true };
|
|
80
|
+
|
|
81
|
+
// Detect write targets: >, >>, tee
|
|
82
|
+
const writeTargets = extractWriteTargets(command);
|
|
83
|
+
for (const target of writeTargets) {
|
|
84
|
+
const resolved = path.isAbsolute(target) ? target : path.resolve(workingDir, target);
|
|
85
|
+
const check = checkWriteAccess(resolved, config, workingDir, projectDir);
|
|
86
|
+
if (!check.allowed) return check;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Detect read targets: cat, less, head, tail, more
|
|
90
|
+
const readTargets = extractReadTargets(command);
|
|
91
|
+
for (const target of readTargets) {
|
|
92
|
+
const resolved = path.isAbsolute(target) ? target : path.resolve(workingDir, target);
|
|
93
|
+
const check = checkReadAccess(resolved, config, workingDir, projectDir);
|
|
94
|
+
if (!check.allowed) return check;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { allowed: true };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Extract file paths that appear as write targets in a shell command */
|
|
101
|
+
function extractWriteTargets(command: string): string[] {
|
|
102
|
+
const targets: string[] = [];
|
|
103
|
+
|
|
104
|
+
// Match >> or > followed by optional space and a file path
|
|
105
|
+
const redirectRegex = />{1,2}\s*([^\s;|&]+)/g;
|
|
106
|
+
let match;
|
|
107
|
+
while ((match = redirectRegex.exec(command)) !== null) {
|
|
108
|
+
targets.push(match[1]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Match tee followed by optional flags and file path
|
|
112
|
+
const teeRegex = /\btee\s+(?:-[a-zA-Z]\s+)*([^\s;|&]+)/g;
|
|
113
|
+
while ((match = teeRegex.exec(command)) !== null) {
|
|
114
|
+
targets.push(match[1]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return targets;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Extract file paths that appear as read targets in a shell command */
|
|
121
|
+
function extractReadTargets(command: string): string[] {
|
|
122
|
+
const targets: string[] = [];
|
|
123
|
+
|
|
124
|
+
// Match cat, less, head, tail, more followed by optional flags and file path
|
|
125
|
+
const readRegex = /\b(?:cat|less|head|tail|more)\s+(?:-[a-zA-Z0-9]+\s+)*([^\s;|&]+)/g;
|
|
126
|
+
let match;
|
|
127
|
+
while ((match = readRegex.exec(command)) !== null) {
|
|
128
|
+
// Skip if it looks like a flag
|
|
129
|
+
if (!match[1].startsWith("-")) {
|
|
130
|
+
targets.push(match[1]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return targets;
|
|
135
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Manager — orchestrates filesystem and network sandbox enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Loads sandbox configuration from .claude/settings.json files using the
|
|
5
|
+
* same hierarchy as the permission system (local → shared → user).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import * as os from "os";
|
|
11
|
+
import type { SandboxConfig, SandboxCheckResult, SandboxMode } from "./types";
|
|
12
|
+
import { defaultSandboxConfig } from "./types";
|
|
13
|
+
import { checkReadAccess, checkWriteAccess, checkShellCommandPaths } from "./filesystem";
|
|
14
|
+
import { checkDomainAccess, buildSandboxedEnv } from "./network";
|
|
15
|
+
|
|
16
|
+
const HOME_DIR = os.homedir();
|
|
17
|
+
const USER_SETTINGS_FILE = path.join(HOME_DIR, ".claude", "settings.json");
|
|
18
|
+
|
|
19
|
+
export class SandboxManager {
|
|
20
|
+
private config: SandboxConfig;
|
|
21
|
+
private workingDir: string;
|
|
22
|
+
private projectDir: string;
|
|
23
|
+
private sessionApprovedDomains = new Set<string>();
|
|
24
|
+
private domainPromptFn: ((domain: string) => Promise<boolean>) | null = null;
|
|
25
|
+
|
|
26
|
+
constructor(workingDir: string, projectDir?: string) {
|
|
27
|
+
this.workingDir = path.resolve(workingDir);
|
|
28
|
+
this.projectDir = projectDir ? path.resolve(projectDir) : this.workingDir;
|
|
29
|
+
this.config = defaultSandboxConfig();
|
|
30
|
+
this.loadConfig();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Config loading ──────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load and merge sandbox config from settings files.
|
|
37
|
+
* Precedence: local project → shared project → user (highest to lowest).
|
|
38
|
+
* Arrays are merged (not replaced) across scopes.
|
|
39
|
+
*/
|
|
40
|
+
loadConfig(): void {
|
|
41
|
+
const candidates = [
|
|
42
|
+
path.join(this.projectDir, ".claude", "settings.local.json"),
|
|
43
|
+
path.join(this.projectDir, ".claude", "settings.json"),
|
|
44
|
+
USER_SETTINGS_FILE,
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const merged = defaultSandboxConfig();
|
|
48
|
+
|
|
49
|
+
for (const filePath of candidates) {
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(filePath)) continue;
|
|
52
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
53
|
+
if (!data.sandbox) continue;
|
|
54
|
+
|
|
55
|
+
const sb = data.sandbox;
|
|
56
|
+
|
|
57
|
+
// enabled: any scope can enable
|
|
58
|
+
if (sb.enabled === true) merged.enabled = true;
|
|
59
|
+
|
|
60
|
+
// mode: highest-precedence scope wins (first found)
|
|
61
|
+
if (sb.mode && merged.mode === "regular") {
|
|
62
|
+
merged.mode = sb.mode as SandboxMode;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Filesystem arrays are merged
|
|
66
|
+
if (sb.filesystem) {
|
|
67
|
+
if (Array.isArray(sb.filesystem.allowWrite)) {
|
|
68
|
+
merged.filesystem.allowWrite.push(...sb.filesystem.allowWrite);
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(sb.filesystem.denyWrite)) {
|
|
71
|
+
merged.filesystem.denyWrite.push(...sb.filesystem.denyWrite);
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(sb.filesystem.denyRead)) {
|
|
74
|
+
merged.filesystem.denyRead.push(...sb.filesystem.denyRead);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Network: merge domains
|
|
79
|
+
if (sb.network) {
|
|
80
|
+
if (Array.isArray(sb.network.allowedDomains)) {
|
|
81
|
+
merged.network.allowedDomains.push(...sb.network.allowedDomains);
|
|
82
|
+
}
|
|
83
|
+
if (sb.network.allowManagedDomainsOnly === true) {
|
|
84
|
+
merged.network.allowManagedDomainsOnly = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Excluded commands: merge
|
|
89
|
+
if (Array.isArray(sb.excludedCommands)) {
|
|
90
|
+
merged.excludedCommands.push(...sb.excludedCommands);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// allowUnsandboxedCommands: false from any scope wins (restrictive)
|
|
94
|
+
if (sb.allowUnsandboxedCommands === false) {
|
|
95
|
+
merged.allowUnsandboxedCommands = false;
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// Skip malformed files
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Deduplicate arrays
|
|
103
|
+
merged.filesystem.allowWrite = [...new Set(merged.filesystem.allowWrite)];
|
|
104
|
+
merged.filesystem.denyWrite = [...new Set(merged.filesystem.denyWrite)];
|
|
105
|
+
merged.filesystem.denyRead = [...new Set(merged.filesystem.denyRead)];
|
|
106
|
+
merged.network.allowedDomains = [...new Set(merged.network.allowedDomains)];
|
|
107
|
+
merged.excludedCommands = [...new Set(merged.excludedCommands)];
|
|
108
|
+
|
|
109
|
+
this.config = merged;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
getConfig(): Readonly<SandboxConfig> {
|
|
115
|
+
return this.config;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
isEnabled(): boolean {
|
|
119
|
+
return this.config.enabled;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getMode(): SandboxMode {
|
|
123
|
+
return this.config.mode;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setDomainPromptFn(fn: (domain: string) => Promise<boolean>): void {
|
|
127
|
+
this.domainPromptFn = fn;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
approveDomain(domain: string): void {
|
|
131
|
+
this.sessionApprovedDomains.add(domain);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Access checks ─────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
checkFileRead(filePath: string): SandboxCheckResult {
|
|
137
|
+
return checkReadAccess(filePath, this.config, this.workingDir, this.projectDir);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
checkFileWrite(filePath: string): SandboxCheckResult {
|
|
141
|
+
return checkWriteAccess(filePath, this.config, this.workingDir, this.projectDir);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if a shell command is allowed by sandbox rules.
|
|
146
|
+
* Returns denied if dangerouslyDisableSandbox is used but not allowed.
|
|
147
|
+
*/
|
|
148
|
+
checkShellCommand(command: string, dangerouslyDisableSandbox?: boolean): SandboxCheckResult {
|
|
149
|
+
if (!this.config.enabled) return { allowed: true };
|
|
150
|
+
|
|
151
|
+
// Handle dangerouslyDisableSandbox escape hatch
|
|
152
|
+
if (dangerouslyDisableSandbox) {
|
|
153
|
+
if (!this.config.allowUnsandboxedCommands) {
|
|
154
|
+
return {
|
|
155
|
+
allowed: false,
|
|
156
|
+
reason: "Sandbox: dangerouslyDisableSandbox is not allowed (allowUnsandboxedCommands is false)",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Allowed to bypass sandbox — permission system still applies
|
|
160
|
+
return { allowed: true };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check if command is excluded from sandbox
|
|
164
|
+
if (this.isExcludedCommand(command)) {
|
|
165
|
+
return { allowed: true };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check command paths heuristically
|
|
169
|
+
return checkShellCommandPaths(command, this.config, this.workingDir, this.projectDir);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check if network access to a URL is allowed.
|
|
174
|
+
* If the domain needs user approval, triggers the domain prompt.
|
|
175
|
+
*/
|
|
176
|
+
async checkNetworkAccess(url: string): Promise<SandboxCheckResult> {
|
|
177
|
+
const result = checkDomainAccess(url, this.config, this.sessionApprovedDomains);
|
|
178
|
+
|
|
179
|
+
if (result.promptUser && result.domain && this.domainPromptFn) {
|
|
180
|
+
const approved = await this.domainPromptFn(result.domain);
|
|
181
|
+
if (approved) {
|
|
182
|
+
this.sessionApprovedDomains.add(result.domain);
|
|
183
|
+
return { allowed: true };
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
allowed: false,
|
|
187
|
+
reason: `Sandbox: user denied network access to domain "${result.domain}"`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Check if a command prefix matches any excluded command.
|
|
196
|
+
*/
|
|
197
|
+
isExcludedCommand(command: string): boolean {
|
|
198
|
+
const trimmed = command.trimStart();
|
|
199
|
+
for (const excluded of this.config.excludedCommands) {
|
|
200
|
+
if (trimmed === excluded || trimmed.startsWith(excluded + " ") || trimmed.startsWith(excluded + "\t")) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get a sandboxed environment for shell command execution.
|
|
209
|
+
*/
|
|
210
|
+
getShellEnv(): NodeJS.ProcessEnv {
|
|
211
|
+
return buildSandboxedEnv(this.config, process.env);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Network — domain access checks and environment restrictions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { SandboxConfig, SandboxCheckResult } from "./types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check whether accessing a URL is allowed by sandbox network rules.
|
|
9
|
+
*/
|
|
10
|
+
export function checkDomainAccess(
|
|
11
|
+
url: string,
|
|
12
|
+
config: SandboxConfig,
|
|
13
|
+
sessionApprovedDomains: Set<string>,
|
|
14
|
+
): SandboxCheckResult {
|
|
15
|
+
if (!config.enabled) return { allowed: true };
|
|
16
|
+
|
|
17
|
+
let hostname: string;
|
|
18
|
+
try {
|
|
19
|
+
hostname = new URL(url).hostname;
|
|
20
|
+
} catch {
|
|
21
|
+
return { allowed: false, reason: `Sandbox: invalid URL "${url}"` };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check against allowed domains (exact or subdomain match)
|
|
25
|
+
if (isDomainAllowed(hostname, config.network.allowedDomains)) {
|
|
26
|
+
return { allowed: true };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check session-approved domains
|
|
30
|
+
if (sessionApprovedDomains.has(hostname)) {
|
|
31
|
+
return { allowed: true };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check if any parent domain was session-approved
|
|
35
|
+
for (const approved of sessionApprovedDomains) {
|
|
36
|
+
if (hostname.endsWith("." + approved)) {
|
|
37
|
+
return { allowed: true };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Domain not allowed
|
|
42
|
+
if (config.network.allowManagedDomainsOnly) {
|
|
43
|
+
return {
|
|
44
|
+
allowed: false,
|
|
45
|
+
reason: `Sandbox: network access denied for domain "${hostname}" (not in allowedDomains and allowManagedDomainsOnly is enabled)`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Prompt user for approval
|
|
50
|
+
return {
|
|
51
|
+
allowed: false,
|
|
52
|
+
promptUser: true,
|
|
53
|
+
domain: hostname,
|
|
54
|
+
reason: `Sandbox: domain "${hostname}" is not in the allowed list`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if a hostname matches any of the allowed domains.
|
|
60
|
+
* Supports exact match and subdomain match (e.g., "github.com" matches "api.github.com").
|
|
61
|
+
*/
|
|
62
|
+
function isDomainAllowed(hostname: string, allowedDomains: string[]): boolean {
|
|
63
|
+
for (const domain of allowedDomains) {
|
|
64
|
+
if (hostname === domain || hostname.endsWith("." + domain)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build a sandboxed environment for shell command execution.
|
|
73
|
+
* Sets proxy env vars as a best-effort network restriction.
|
|
74
|
+
*/
|
|
75
|
+
export function buildSandboxedEnv(
|
|
76
|
+
config: SandboxConfig,
|
|
77
|
+
baseEnv: NodeJS.ProcessEnv,
|
|
78
|
+
): NodeJS.ProcessEnv {
|
|
79
|
+
if (!config.enabled) return { ...baseEnv };
|
|
80
|
+
|
|
81
|
+
const env = { ...baseEnv };
|
|
82
|
+
|
|
83
|
+
// Remove potentially dangerous env vars that could leak credentials
|
|
84
|
+
const sensitiveVars = [
|
|
85
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
86
|
+
"AWS_SESSION_TOKEN",
|
|
87
|
+
"GH_TOKEN",
|
|
88
|
+
"GITHUB_TOKEN",
|
|
89
|
+
"NPM_TOKEN",
|
|
90
|
+
"DOCKER_PASSWORD",
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
for (const v of sensitiveVars) {
|
|
94
|
+
// Only remove if sandbox is actively restricting network
|
|
95
|
+
if (config.network.allowedDomains.length > 0 || config.network.allowManagedDomainsOnly) {
|
|
96
|
+
delete env[v];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return env;
|
|
101
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Types — configuration and check result interfaces.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface SandboxFilesystemConfig {
|
|
6
|
+
/** Additional paths (beyond workingDir) where writes are allowed */
|
|
7
|
+
allowWrite: string[];
|
|
8
|
+
/** Paths where writes are denied */
|
|
9
|
+
denyWrite: string[];
|
|
10
|
+
/** Paths where reads are denied */
|
|
11
|
+
denyRead: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SandboxNetworkConfig {
|
|
15
|
+
/** Whitelisted domains for network access */
|
|
16
|
+
allowedDomains: string[];
|
|
17
|
+
/** If true, block non-allowed domains without prompting the user */
|
|
18
|
+
allowManagedDomainsOnly: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Sandbox operation mode */
|
|
22
|
+
export type SandboxMode = "auto-allow" | "regular";
|
|
23
|
+
|
|
24
|
+
/** Full sandbox configuration (loaded from settings files) */
|
|
25
|
+
export interface SandboxConfig {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
mode: SandboxMode;
|
|
28
|
+
filesystem: SandboxFilesystemConfig;
|
|
29
|
+
network: SandboxNetworkConfig;
|
|
30
|
+
/** Commands that bypass the sandbox (e.g. "docker") */
|
|
31
|
+
excludedCommands: string[];
|
|
32
|
+
/** Whether dangerouslyDisableSandbox param is respected */
|
|
33
|
+
allowUnsandboxedCommands: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Result of a sandbox access check */
|
|
37
|
+
export interface SandboxCheckResult {
|
|
38
|
+
allowed: boolean;
|
|
39
|
+
reason?: string;
|
|
40
|
+
/** For network checks: whether the user should be prompted about a new domain */
|
|
41
|
+
promptUser?: boolean;
|
|
42
|
+
/** The domain being requested (when promptUser is true) */
|
|
43
|
+
domain?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Returns a default (disabled) sandbox config */
|
|
47
|
+
export function defaultSandboxConfig(): SandboxConfig {
|
|
48
|
+
return {
|
|
49
|
+
enabled: false,
|
|
50
|
+
mode: "regular",
|
|
51
|
+
filesystem: {
|
|
52
|
+
allowWrite: [],
|
|
53
|
+
denyWrite: [],
|
|
54
|
+
denyRead: [],
|
|
55
|
+
},
|
|
56
|
+
network: {
|
|
57
|
+
allowedDomains: [],
|
|
58
|
+
allowManagedDomainsOnly: false,
|
|
59
|
+
},
|
|
60
|
+
excludedCommands: [],
|
|
61
|
+
allowUnsandboxedCommands: true,
|
|
62
|
+
};
|
|
63
|
+
}
|