-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
142 lines (129 loc) · 4.78 KB
/
Copy pathindex.ts
File metadata and controls
142 lines (129 loc) · 4.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Sandbox subsystem entry — wraps Bash invocations under macOS sandbox-exec or
// Linux bwrap based on settings.sandbox + platform.
// Spec: docs/DEVELOPMENT_PLAN.md §3.9a
// Milestone: M3.5
import { promises as fs } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import type { SandboxConfig, SandboxMode } from '../config/types.js';
import { resolveSandboxMode, sandboxConfigForMode } from './policy.js';
import { allClausesExcluded } from './pipeline.js';
import { buildLinuxBwrapArgs, buildMacOsProfile, detectPlatform } from './profile.js';
export {
buildMacOsProfile,
buildLinuxBwrapArgs,
detectPlatform,
type SandboxPlatform,
} from './profile.js';
export { splitClauses, allClausesExcluded, type Clause } from './pipeline.js';
export {
startDnsProxy,
parseQName,
buildNxDomain,
type DnsProxyOpts,
type DnsProxyHandle,
} from './dns-proxy.js';
export {
spawnNetworkSandbox,
needsNetworkSandbox,
denyAllNetwork,
NetworkSandboxUnavailable,
type SpawnNetworkSandboxOpts,
type NetworkSandboxHandle,
} from './netns.js';
export type { BwrapArgsOpts } from './profile.js';
export interface SandboxedCommand {
/** Command + args to spawn (the actual sandbox wrapper invocation). */
command: string;
args: string[];
}
/**
* Wrap a user-supplied shell command under platform sandbox.
*
* Returns the wrapped (command, args) to pass to child_process.spawn.
* If sandbox is disabled OR the platform is unsupported, returns the
* unwrapped equivalent of /bin/sh -c <userCommand>.
*
* Also honors `excludedCommands` — commands whose argv[0] matches an excluded
* entry bypass the sandbox. Useful for `git` (which needs broad fs access).
*/
export async function wrapBashCommand(args: {
userCommand: string;
cwd: string;
config: SandboxConfig | undefined;
/**
* Mode to apply when the config names none. Hosts pass the resolved policy;
* library callers that omit it keep the historical "off unless configured"
* behaviour so an embedder can't be silently sandboxed by an upgrade.
*/
defaultMode?: SandboxMode;
}): Promise<SandboxedCommand> {
const mode = resolveSandboxMode(args.config, args.defaultMode ?? 'danger-full-access');
const config = sandboxConfigForMode(args.config, mode, args.cwd, await linkedGitDirs(args.cwd));
if (!config?.enabled) {
return { command: '/bin/sh', args: ['-c', args.userCommand] };
}
// Excluded commands: skip sandbox ONLY if EVERY clause in the pipeline is
// an excluded command. `git status && rm -rf /` does not bypass because
// `rm` isn't excluded.
const excluded = config.excludedCommands ?? [];
if (excluded.length > 0 && allClausesExcluded(args.userCommand, excluded)) {
return { command: '/bin/sh', args: ['-c', args.userCommand] };
}
const platform = detectPlatform();
if (platform === 'macos') {
const profile = buildMacOsProfile(config, args.cwd);
const profilePath = join(tmpdir(), `deepcode-sb-${process.pid}-${Date.now().toString(36)}.sb`);
await fs.writeFile(profilePath, profile, 'utf8');
return {
command: 'sandbox-exec',
args: ['-f', profilePath, '/bin/sh', '-c', args.userCommand],
};
}
if (platform === 'linux') {
const bwrapArgs = buildLinuxBwrapArgs(config, args.cwd);
return {
command: 'bwrap',
args: [...bwrapArgs, '/bin/sh', '-c', args.userCommand],
};
}
// Windows / unsupported: explicit per §0.2 — sandbox disabled, run unwrapped
return { command: '/bin/sh', args: ['-c', args.userCommand] };
}
/**
* The git directories a workspace needs but doesn't contain.
*
* In a linked worktree `.git` is a file pointing at
* `<main>/.git/worktrees/<name>`, and the shared object store lives one level
* up again — both outside cwd. Sandboxing the workspace without them breaks
* every git command in exactly the worktrees DeepCode's own EnterWorktree tool
* creates. Best-effort: any read failure just yields no extra paths.
*/
async function linkedGitDirs(cwd: string): Promise<string[]> {
try {
const pointer = await fs.readFile(join(cwd, '.git'), 'utf8');
const match = /^gitdir:\s*(.+)$/m.exec(pointer);
if (!match) return [];
const gitDir = resolve(cwd, match[1]!.trim());
const dirs = [gitDir];
try {
const commonDir = (await fs.readFile(join(gitDir, 'commondir'), 'utf8')).trim();
if (commonDir) dirs.push(resolve(gitDir, commonDir));
} catch {
/* no commondir — a plain gitdir pointer */
}
return dirs;
} catch {
// `.git` is a directory (ordinary repo) or absent — nothing extra needed.
return [];
}
}
export { withAdditionalWritableDirs } from './additional-dirs.js';
export {
SANDBOX_MODES,
isSandboxMode,
resolveSandboxMode,
sandboxConfigForMode,
describeSandboxMode,
withSandboxMode,
} from './policy.js';