@commonlyai/cli 0.1.2 → 0.1.5
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/skills/commonly/SKILL.md +8 -0
- package/src/commands/agent.js +60 -5
- package/src/lib/adapters/claude.js +323 -67
- package/src/lib/adapters/codex.js +185 -21
- package/src/lib/environment.js +95 -52
- package/src/lib/sandbox/bwrap.js +11 -0
- package/src/lib/sandbox/seatbelt.js +417 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS Seatbelt wrapper for public Claude Code agents.
|
|
3
|
+
*
|
|
4
|
+
* `sandbox-exec` is deprecated as a public API, but remains the host-native
|
|
5
|
+
* kernel boundary available to local CLI wrappers on current macOS. This
|
|
6
|
+
* profile starts from deny-by-default, admits only the system runtime Claude
|
|
7
|
+
* needs, and then grants explicit access to:
|
|
8
|
+
* - the selected Claude executable,
|
|
9
|
+
* - one workspace (read/write or read-only),
|
|
10
|
+
* - one isolated, token-free Claude state directory, and
|
|
11
|
+
* - the adapter-owned transient MCP config directory (read-only).
|
|
12
|
+
*
|
|
13
|
+
* The static platform baseline is adapted from OpenAI Codex's Apache-2.0
|
|
14
|
+
* Seatbelt policy:
|
|
15
|
+
* https://github.com/openai/codex/tree/main/codex-rs/sandboxing/src
|
|
16
|
+
*
|
|
17
|
+
* Public Claude still needs outbound network access for the Anthropic API and
|
|
18
|
+
* declared MCP transports. Host confidentiality therefore comes from the
|
|
19
|
+
* filesystem boundary, not a claim that all network syscalls are disabled.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { spawnSync } from 'child_process';
|
|
23
|
+
import { realpathSync } from 'fs';
|
|
24
|
+
import { homedir } from 'os';
|
|
25
|
+
import { isAbsolute, join } from 'path';
|
|
26
|
+
|
|
27
|
+
const SANDBOX_EXEC = '/usr/bin/sandbox-exec';
|
|
28
|
+
const DARWIN_MESSAGE = 'Seatbelt sandboxing is available only on macOS';
|
|
29
|
+
|
|
30
|
+
// Keep this baseline static and auditable. Dynamic paths are appended by
|
|
31
|
+
// buildSeatbeltProfile after strict absolute-path validation + SBPL escaping.
|
|
32
|
+
const PLATFORM_BASELINE = String.raw`
|
|
33
|
+
(version 1)
|
|
34
|
+
(deny default)
|
|
35
|
+
|
|
36
|
+
; Children inherit the same sandbox. Process inspection is limited to this
|
|
37
|
+
; sandbox, so a model cannot inspect unrelated operator processes.
|
|
38
|
+
(allow process-exec)
|
|
39
|
+
(allow process-fork)
|
|
40
|
+
(allow signal (target same-sandbox))
|
|
41
|
+
(allow process-info* (target same-sandbox))
|
|
42
|
+
|
|
43
|
+
(allow file-write-data
|
|
44
|
+
(require-all
|
|
45
|
+
(path "/dev/null")
|
|
46
|
+
(vnode-type CHARACTER-DEVICE)))
|
|
47
|
+
|
|
48
|
+
; Loader + standard system resources.
|
|
49
|
+
(allow file-read* file-test-existence
|
|
50
|
+
(subpath "/Library/Apple")
|
|
51
|
+
(subpath "/Library/Filesystems/NetFSPlugins")
|
|
52
|
+
(subpath "/Library/Preferences/Logging")
|
|
53
|
+
(subpath "/private/var/db/DarwinDirectory/local/recordStore.data")
|
|
54
|
+
(subpath "/private/var/db/timezone")
|
|
55
|
+
(subpath "/usr/lib")
|
|
56
|
+
(subpath "/usr/share")
|
|
57
|
+
(subpath "/Library/Preferences")
|
|
58
|
+
(subpath "/Library/Keychains")
|
|
59
|
+
(subpath "/var/db")
|
|
60
|
+
(subpath "/private/var/db"))
|
|
61
|
+
(allow file-read* file-test-existence
|
|
62
|
+
(subpath "/System/Volumes/Preboot/Cryptexes/OS"))
|
|
63
|
+
|
|
64
|
+
(allow file-map-executable
|
|
65
|
+
(subpath "/Library/Apple/System/Library/Frameworks")
|
|
66
|
+
(subpath "/Library/Apple/System/Library/PrivateFrameworks")
|
|
67
|
+
(subpath "/Library/Apple/usr/lib")
|
|
68
|
+
(subpath "/System/Library/Extensions")
|
|
69
|
+
(subpath "/System/Library/Frameworks")
|
|
70
|
+
(subpath "/System/Library/PrivateFrameworks")
|
|
71
|
+
(subpath "/System/Library/SubFrameworks")
|
|
72
|
+
(subpath "/System/iOSSupport/System/Library/Frameworks")
|
|
73
|
+
(subpath "/System/iOSSupport/System/Library/PrivateFrameworks")
|
|
74
|
+
(subpath "/System/iOSSupport/System/Library/SubFrameworks")
|
|
75
|
+
(subpath "/usr/lib"))
|
|
76
|
+
|
|
77
|
+
(allow file-read* file-test-existence
|
|
78
|
+
(subpath "/Library/Apple/System/Library/Frameworks")
|
|
79
|
+
(subpath "/Library/Apple/System/Library/PrivateFrameworks")
|
|
80
|
+
(subpath "/Library/Apple/usr/lib")
|
|
81
|
+
(subpath "/System/Library/Frameworks")
|
|
82
|
+
(subpath "/System/Library/PrivateFrameworks")
|
|
83
|
+
(subpath "/System/Library/SubFrameworks")
|
|
84
|
+
(subpath "/System/iOSSupport/System/Library/Frameworks")
|
|
85
|
+
(subpath "/System/iOSSupport/System/Library/PrivateFrameworks")
|
|
86
|
+
(subpath "/System/iOSSupport/System/Library/SubFrameworks")
|
|
87
|
+
(subpath "/usr/lib"))
|
|
88
|
+
|
|
89
|
+
(allow system-mac-syscall (mac-policy-name "vnguard"))
|
|
90
|
+
(allow system-mac-syscall
|
|
91
|
+
(require-all
|
|
92
|
+
(mac-policy-name "Sandbox")
|
|
93
|
+
(mac-syscall-number 67)))
|
|
94
|
+
|
|
95
|
+
(allow file-read-metadata file-test-existence
|
|
96
|
+
(literal "/etc")
|
|
97
|
+
(literal "/tmp")
|
|
98
|
+
(literal "/var")
|
|
99
|
+
(literal "/private/etc/localtime")
|
|
100
|
+
(path-ancestors "/System/Volumes/Data/private"))
|
|
101
|
+
(allow file-read* file-test-existence (literal "/"))
|
|
102
|
+
|
|
103
|
+
(allow file-read* file-test-existence
|
|
104
|
+
(literal "/dev/autofs_nowait")
|
|
105
|
+
(literal "/dev/random")
|
|
106
|
+
(literal "/dev/urandom")
|
|
107
|
+
(literal "/private/etc/master.passwd")
|
|
108
|
+
(literal "/private/etc/passwd")
|
|
109
|
+
(literal "/private/etc/protocols")
|
|
110
|
+
(literal "/private/etc/services"))
|
|
111
|
+
(allow file-read* file-test-existence file-write-data file-ioctl
|
|
112
|
+
(literal "/dev/dtracehelper"))
|
|
113
|
+
(allow file-read* file-test-existence file-write-data file-ioctl
|
|
114
|
+
(literal "/dev/null")
|
|
115
|
+
(literal "/dev/zero"))
|
|
116
|
+
(allow file-read-data file-test-existence file-write-data (subpath "/dev/fd"))
|
|
117
|
+
|
|
118
|
+
; System configuration is readable; user-home configuration is not.
|
|
119
|
+
(allow file-read* (subpath "/etc"))
|
|
120
|
+
(allow file-read* (subpath "/private/etc"))
|
|
121
|
+
(allow file-read* file-test-existence
|
|
122
|
+
(literal "/System/Library/CoreServices")
|
|
123
|
+
(literal "/System/Library/CoreServices/.SystemVersionPlatform.plist")
|
|
124
|
+
(literal "/System/Library/CoreServices/SystemVersion.plist"))
|
|
125
|
+
(allow file-read-metadata file-test-existence
|
|
126
|
+
(literal "/Library")
|
|
127
|
+
(literal "/Library/Application Support")
|
|
128
|
+
(literal "/System/Library")
|
|
129
|
+
(literal "/System/Cryptexes")
|
|
130
|
+
(literal "/System/Cryptexes/App")
|
|
131
|
+
(literal "/System/Cryptexes/OS"))
|
|
132
|
+
(allow file-read-metadata (subpath "/var"))
|
|
133
|
+
(allow file-read-metadata (subpath "/private/var"))
|
|
134
|
+
|
|
135
|
+
; The native Claude binary and declared MCP launchers may execute children.
|
|
136
|
+
; Claude bootstraps stdio MCP servers through /bin/sh, whose interpreter is
|
|
137
|
+
; /bin/bash on current macOS. An executable-level shell deny would therefore
|
|
138
|
+
; disable the declared capability too. The built-in Bash tool is denied in
|
|
139
|
+
; Claude argv; any trusted child that does run inherits this same Seatbelt
|
|
140
|
+
; profile, so host-secret reads and out-of-workspace writes remain kernel
|
|
141
|
+
; denied.
|
|
142
|
+
|
|
143
|
+
(allow file-read-data file-read-metadata (subpath "/bin"))
|
|
144
|
+
(allow file-read-data file-read-metadata (subpath "/sbin"))
|
|
145
|
+
(allow file-read-data file-read-metadata (subpath "/usr/bin"))
|
|
146
|
+
(allow file-read-data file-read-metadata (subpath "/usr/sbin"))
|
|
147
|
+
(allow file-read-data file-read-metadata (subpath "/usr/libexec"))
|
|
148
|
+
(allow file-read* file-test-existence (subpath "/opt/homebrew/bin"))
|
|
149
|
+
(allow file-read* file-test-existence file-map-executable
|
|
150
|
+
(subpath "/opt/homebrew/Cellar"))
|
|
151
|
+
(allow file-read* file-test-existence
|
|
152
|
+
(subpath "/opt/homebrew/etc/openssl@3"))
|
|
153
|
+
(allow file-read* (subpath "/opt/homebrew/lib"))
|
|
154
|
+
(allow file-read* file-test-existence file-map-executable
|
|
155
|
+
(subpath "/opt/homebrew/opt"))
|
|
156
|
+
(allow file-read* file-test-existence (subpath "/usr/local/bin"))
|
|
157
|
+
(allow file-read* (subpath "/usr/local/lib"))
|
|
158
|
+
|
|
159
|
+
; Standard device handles and PTY support.
|
|
160
|
+
(allow pseudo-tty)
|
|
161
|
+
(allow file-read* file-write* file-ioctl (literal "/dev/ptmx"))
|
|
162
|
+
(allow file-read* file-write*
|
|
163
|
+
(require-all
|
|
164
|
+
(regex #"^/dev/ttys[0-9]+")
|
|
165
|
+
(extension "com.apple.sandbox.pty")))
|
|
166
|
+
(allow file-ioctl (regex #"^/dev/ttys[0-9]+"))
|
|
167
|
+
(allow file-read* (regex "^/dev/fd/(0|1|2)$"))
|
|
168
|
+
(allow file-write* (regex "^/dev/fd/(1|2)$"))
|
|
169
|
+
(allow file-read* file-write* (literal "/dev/tty"))
|
|
170
|
+
(allow file-read-metadata (literal "/dev"))
|
|
171
|
+
(allow file-read-metadata (regex "^/dev/.*$"))
|
|
172
|
+
(allow file-read-metadata (literal "/dev/stdin"))
|
|
173
|
+
(allow file-read-metadata (literal "/dev/stdout"))
|
|
174
|
+
(allow file-read-metadata (literal "/dev/stderr"))
|
|
175
|
+
|
|
176
|
+
; Runtime queries used by native binaries and Node/Bun.
|
|
177
|
+
(allow sysctl-read
|
|
178
|
+
(sysctl-name "hw.activecpu")
|
|
179
|
+
(sysctl-name "hw.busfrequency_compat")
|
|
180
|
+
(sysctl-name "hw.byteorder")
|
|
181
|
+
(sysctl-name "hw.cacheconfig")
|
|
182
|
+
(sysctl-name "hw.cachelinesize_compat")
|
|
183
|
+
(sysctl-name "hw.cpufamily")
|
|
184
|
+
(sysctl-name "hw.cpufrequency")
|
|
185
|
+
(sysctl-name "hw.cpufrequency_compat")
|
|
186
|
+
(sysctl-name "hw.cputype")
|
|
187
|
+
(sysctl-name "hw.ephemeral_storage")
|
|
188
|
+
(sysctl-name "hw.l1dcachesize_compat")
|
|
189
|
+
(sysctl-name "hw.l1icachesize_compat")
|
|
190
|
+
(sysctl-name "hw.l2cachesize_compat")
|
|
191
|
+
(sysctl-name "hw.l3cachesize_compat")
|
|
192
|
+
(sysctl-name "hw.logicalcpu")
|
|
193
|
+
(sysctl-name "hw.logicalcpu_max")
|
|
194
|
+
(sysctl-name "hw.machine")
|
|
195
|
+
(sysctl-name "hw.memsize")
|
|
196
|
+
(sysctl-name "hw.model")
|
|
197
|
+
(sysctl-name "hw.ncpu")
|
|
198
|
+
(sysctl-name "hw.packages")
|
|
199
|
+
(sysctl-name "hw.pagesize")
|
|
200
|
+
(sysctl-name "hw.pagesize_compat")
|
|
201
|
+
(sysctl-name "hw.physicalcpu")
|
|
202
|
+
(sysctl-name "hw.physicalcpu_max")
|
|
203
|
+
(sysctl-name "hw.tbfrequency_compat")
|
|
204
|
+
(sysctl-name "hw.vectorunit")
|
|
205
|
+
(sysctl-name-prefix "hw.optional.arm.")
|
|
206
|
+
(sysctl-name-prefix "hw.optional.armv8_")
|
|
207
|
+
(sysctl-name-prefix "hw.perflevel")
|
|
208
|
+
(sysctl-name "kern.argmax")
|
|
209
|
+
(sysctl-name "kern.bootargs")
|
|
210
|
+
(sysctl-name "kern.hostname")
|
|
211
|
+
(sysctl-name "kern.iossupportversion")
|
|
212
|
+
(sysctl-name "kern.maxfilesperproc")
|
|
213
|
+
(sysctl-name "kern.maxproc")
|
|
214
|
+
(sysctl-name "kern.osproductversion")
|
|
215
|
+
(sysctl-name "kern.osrelease")
|
|
216
|
+
(sysctl-name "kern.ostype")
|
|
217
|
+
(sysctl-name "kern.osvariant_status")
|
|
218
|
+
(sysctl-name "kern.osversion")
|
|
219
|
+
(sysctl-name "kern.secure_kernel")
|
|
220
|
+
(sysctl-name "kern.usrstack64")
|
|
221
|
+
(sysctl-name "kern.version")
|
|
222
|
+
(sysctl-name "machdep.cpu.brand_string")
|
|
223
|
+
(sysctl-name "security.mac.lockdown_mode_state")
|
|
224
|
+
(sysctl-name "sysctl.proc_cputype")
|
|
225
|
+
(sysctl-name "vm.loadavg")
|
|
226
|
+
(sysctl-name-prefix "kern.proc.pgrp.")
|
|
227
|
+
(sysctl-name-prefix "kern.proc.pid.")
|
|
228
|
+
(sysctl-name-prefix "net.routetable."))
|
|
229
|
+
(allow sysctl-write (sysctl-name "kern.grade_cputype"))
|
|
230
|
+
|
|
231
|
+
(allow iokit-open (iokit-registry-entry-class "RootDomainUserClient"))
|
|
232
|
+
(allow ipc-posix-sem)
|
|
233
|
+
(allow ipc-posix-shm-read-data ipc-posix-shm-write-create ipc-posix-shm-write-data
|
|
234
|
+
(ipc-posix-name "com.apple.AppleDatabaseChanged"))
|
|
235
|
+
(allow ipc-posix-shm-read-data
|
|
236
|
+
ipc-posix-shm-write-create
|
|
237
|
+
ipc-posix-shm-write-unlink
|
|
238
|
+
(ipc-posix-name-regex #"^/__KMP_REGISTERED_LIB_[0-9]+$"))
|
|
239
|
+
(allow ipc-posix-shm-read* (ipc-posix-name-prefix "apple.cfprefs."))
|
|
240
|
+
(allow ipc-posix-shm-read*
|
|
241
|
+
(ipc-posix-name "apple.shm.notification_center"))
|
|
242
|
+
(allow mach-lookup
|
|
243
|
+
(global-name "com.apple.PowerManagement.control")
|
|
244
|
+
(global-name "com.apple.analyticsd")
|
|
245
|
+
(global-name "com.apple.analyticsd.messagetracer")
|
|
246
|
+
(global-name "com.apple.appsleep")
|
|
247
|
+
(global-name "com.apple.bsd.dirhelper")
|
|
248
|
+
(global-name "com.apple.cfprefsd.agent")
|
|
249
|
+
(global-name "com.apple.cfprefsd.daemon")
|
|
250
|
+
(global-name "com.apple.diagnosticd")
|
|
251
|
+
(global-name "com.apple.FSEvents")
|
|
252
|
+
(global-name "com.apple.logd")
|
|
253
|
+
(global-name "com.apple.logd.events")
|
|
254
|
+
(global-name "com.apple.runningboard")
|
|
255
|
+
(global-name "com.apple.secinitd")
|
|
256
|
+
(global-name "com.apple.system.DirectoryService.libinfo_v1")
|
|
257
|
+
(global-name "com.apple.system.logger")
|
|
258
|
+
(global-name "com.apple.system.notification_center")
|
|
259
|
+
(global-name "com.apple.system.opendirectoryd.libinfo")
|
|
260
|
+
(global-name "com.apple.system.opendirectoryd.membership")
|
|
261
|
+
(global-name "com.apple.trustd")
|
|
262
|
+
(global-name "com.apple.trustd.agent")
|
|
263
|
+
(global-name "com.apple.xpc.activity.unmanaged")
|
|
264
|
+
(local-name "com.apple.cfprefsd.agent"))
|
|
265
|
+
(allow user-preference-read)
|
|
266
|
+
|
|
267
|
+
; Claude itself and declared MCP transports need outbound network. The model's
|
|
268
|
+
; WebSearch/WebFetch/Bash tools are denied independently in the adapter argv.
|
|
269
|
+
(allow network-outbound)
|
|
270
|
+
(allow system-socket
|
|
271
|
+
(require-all
|
|
272
|
+
(socket-domain AF_SYSTEM)
|
|
273
|
+
(socket-protocol 2)))
|
|
274
|
+
(allow mach-lookup
|
|
275
|
+
(global-name "com.apple.SecurityServer")
|
|
276
|
+
(global-name "com.apple.SystemConfiguration.DNSConfiguration")
|
|
277
|
+
(global-name "com.apple.SystemConfiguration.configd")
|
|
278
|
+
(global-name "com.apple.networkd")
|
|
279
|
+
(global-name "com.apple.ocspd")
|
|
280
|
+
(global-name "com.apple.securityd")
|
|
281
|
+
(global-name "com.apple.securityd.xpc"))
|
|
282
|
+
`;
|
|
283
|
+
|
|
284
|
+
const escapeSbplString = (value) => String(value)
|
|
285
|
+
.replaceAll('\\', '\\\\')
|
|
286
|
+
.replaceAll('"', '\\"')
|
|
287
|
+
.replaceAll('\n', '\\n')
|
|
288
|
+
.replaceAll('\r', '\\r');
|
|
289
|
+
|
|
290
|
+
const literal = (path) => `(literal "${escapeSbplString(path)}")`;
|
|
291
|
+
const subpath = (path) => `(subpath "${escapeSbplString(path)}")`;
|
|
292
|
+
|
|
293
|
+
const assertAbsolutePath = (path, label) => {
|
|
294
|
+
if (!path || !isAbsolute(path)) {
|
|
295
|
+
throw new Error(`Seatbelt ${label} must be an absolute path`);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const realpathOrSelf = (path) => {
|
|
300
|
+
try {
|
|
301
|
+
return realpathSync(path);
|
|
302
|
+
} catch {
|
|
303
|
+
return path;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
export const detectSeatbelt = () => {
|
|
308
|
+
if (process.platform !== 'darwin') {
|
|
309
|
+
return { available: false, error: DARWIN_MESSAGE };
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
const result = spawnSync(
|
|
313
|
+
SANDBOX_EXEC,
|
|
314
|
+
['-p', '(version 1)(allow default)', '/usr/bin/true'],
|
|
315
|
+
{ encoding: 'utf8' },
|
|
316
|
+
);
|
|
317
|
+
if (result.error || result.status !== 0) {
|
|
318
|
+
return {
|
|
319
|
+
available: false,
|
|
320
|
+
error: `sandbox-exec preflight failed: ${result.error?.message || result.stderr?.trim() || `exit ${result.status}`}`,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return { available: true, path: SANDBOX_EXEC };
|
|
324
|
+
} catch (err) {
|
|
325
|
+
return { available: false, error: `sandbox-exec detection failed: ${err.message}` };
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export const buildSeatbeltProfile = ({
|
|
330
|
+
workspacePath,
|
|
331
|
+
workspaceAccess = 'write',
|
|
332
|
+
claudePath,
|
|
333
|
+
statePath,
|
|
334
|
+
mcpConfigDir = null,
|
|
335
|
+
executablePaths = [],
|
|
336
|
+
}) => {
|
|
337
|
+
for (const [path, label] of [
|
|
338
|
+
[workspacePath, 'workspacePath'],
|
|
339
|
+
[claudePath, 'claudePath'],
|
|
340
|
+
[statePath, 'statePath'],
|
|
341
|
+
]) {
|
|
342
|
+
assertAbsolutePath(path, label);
|
|
343
|
+
}
|
|
344
|
+
if (mcpConfigDir) assertAbsolutePath(mcpConfigDir, 'mcpConfigDir');
|
|
345
|
+
if (!['read', 'write'].includes(workspaceAccess)) {
|
|
346
|
+
throw new Error('Seatbelt workspaceAccess must be read or write');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const resolvedWorkspace = realpathOrSelf(workspacePath);
|
|
350
|
+
const resolvedClaude = realpathOrSelf(claudePath);
|
|
351
|
+
const resolvedState = realpathOrSelf(statePath);
|
|
352
|
+
const resolvedMcpConfig = mcpConfigDir ? realpathOrSelf(mcpConfigDir) : null;
|
|
353
|
+
const executables = [...new Set(
|
|
354
|
+
[resolvedClaude, process.execPath, ...executablePaths]
|
|
355
|
+
.filter(Boolean)
|
|
356
|
+
.map(realpathOrSelf),
|
|
357
|
+
)];
|
|
358
|
+
for (const executable of executables) {
|
|
359
|
+
assertAbsolutePath(executable, 'executable path');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const workspaceRule = workspaceAccess === 'write'
|
|
363
|
+
? `(allow file-read* file-test-existence file-write* ${subpath(resolvedWorkspace)})`
|
|
364
|
+
: `(allow file-read* file-test-existence ${subpath(resolvedWorkspace)})`;
|
|
365
|
+
const executableRules = executables.map((executable) => [
|
|
366
|
+
`(allow file-read* file-test-existence file-map-executable ${literal(executable)})`,
|
|
367
|
+
`(allow file-read-metadata file-test-existence (path-ancestors "${escapeSbplString(executable)}"))`,
|
|
368
|
+
].join('\n')).join('\n');
|
|
369
|
+
const mcpRule = resolvedMcpConfig
|
|
370
|
+
? `(allow file-read* file-test-existence ${subpath(resolvedMcpConfig)})`
|
|
371
|
+
: '';
|
|
372
|
+
const claudeTmp = `/private/tmp/claude-${typeof process.getuid === 'function' ? process.getuid() : '0'}`;
|
|
373
|
+
const userKeychains = join(homedir(), 'Library', 'Keychains');
|
|
374
|
+
const textEncoding = join(homedir(), '.CFUserTextEncoding');
|
|
375
|
+
|
|
376
|
+
return [
|
|
377
|
+
PLATFORM_BASELINE.trim(),
|
|
378
|
+
executableRules,
|
|
379
|
+
`(allow file-read-metadata file-test-existence (path-ancestors "${escapeSbplString(resolvedWorkspace)}"))`,
|
|
380
|
+
`(allow file-read-metadata file-test-existence (path-ancestors "${escapeSbplString(resolvedState)}"))`,
|
|
381
|
+
workspaceRule,
|
|
382
|
+
`(allow file-read* file-test-existence file-write* ${subpath(resolvedState)})`,
|
|
383
|
+
mcpRule,
|
|
384
|
+
// The workspace is intentionally readable, but credential-like nested
|
|
385
|
+
// paths are not. Explicit deny wins over the broad workspace allow.
|
|
386
|
+
`(deny file-read* file-write* ${subpath(join(resolvedWorkspace, '.commonly'))})`,
|
|
387
|
+
`(deny file-read* file-write* ${subpath(join(resolvedWorkspace, '.codex'))})`,
|
|
388
|
+
`(deny file-read* file-write* ${literal(join(resolvedWorkspace, '.env'))})`,
|
|
389
|
+
// Native Claude uses a uid-scoped /private/tmp directory even when
|
|
390
|
+
// TMPDIR points elsewhere. Admit that exact runtime directory; every
|
|
391
|
+
// sibling under /private/tmp remains denied by the default policy.
|
|
392
|
+
`(allow file-read-metadata file-test-existence (path-ancestors "${escapeSbplString(claudeTmp)}"))`,
|
|
393
|
+
`(allow file-read* file-test-existence file-write* ${literal(claudeTmp)} ${subpath(claudeTmp)})`,
|
|
394
|
+
// OAuth remains in macOS Keychain rather than a bearer-token file or
|
|
395
|
+
// child environment variable. Claude's `security` helper needs the
|
|
396
|
+
// encrypted login-keychain database plus this non-secret locale file.
|
|
397
|
+
`(allow file-read* file-test-existence ${subpath(userKeychains)} ${literal(textEncoding)})`,
|
|
398
|
+
`(allow file-read-metadata file-test-existence (path-ancestors "${escapeSbplString(userKeychains)}"))`,
|
|
399
|
+
].filter(Boolean).join('\n\n');
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
export const wrapArgvWithSeatbelt = (innerArgv, opts = {}) => {
|
|
403
|
+
if (process.platform !== 'darwin') {
|
|
404
|
+
throw new Error(DARWIN_MESSAGE);
|
|
405
|
+
}
|
|
406
|
+
if (!Array.isArray(innerArgv) || innerArgv.length === 0) {
|
|
407
|
+
throw new Error('wrapArgvWithSeatbelt requires a non-empty innerArgv');
|
|
408
|
+
}
|
|
409
|
+
const profile = buildSeatbeltProfile(opts);
|
|
410
|
+
return [SANDBOX_EXEC, '-p', profile, ...innerArgv];
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
export const publicClaudeStateRoot = (agentKey) => (
|
|
414
|
+
join(homedir(), '.commonly', 'claude-homes', agentKey)
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
export const SEATBELT_DARWIN_MESSAGE = DARWIN_MESSAGE;
|