-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclaude-code.sb
More file actions
272 lines (239 loc) · 12.3 KB
/
Copy pathclaude-code.sb
File metadata and controls
272 lines (239 loc) · 12.3 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
(version 1)
(deny default)
;; ===== Process =====
(allow process-exec*)
(allow process-fork)
(allow process-info*)
(allow signal)
(allow sysctl-read)
(allow pseudo-tty)
;; ===== File metadata (needed for stat/readdir everywhere, no content exposed) =====
(allow file-read-metadata)
;; L2: Scope xattr reads to paths already reachable via file-read-data rather
;; than granting them system-wide (which would expose xattrs on ~/.ssh, ~/.aws, etc.).
(allow file-read-xattr
(subpath "/usr")
(subpath "/bin")
(subpath "/sbin")
(subpath "/System")
(subpath "/Library")
(subpath "/dev")
(subpath "/private/var")
(subpath "/private/etc")
(subpath "/private/tmp")
(subpath "/opt/homebrew")
(subpath "/Applications/Xcode.app")
(subpath (string-append (param "HOME") "/.local"))
(subpath (string-append (param "HOME") "/.claude"))
(subpath (string-append (param "HOME") "/.nvm"))
(subpath (string-append (param "HOME") "/Library"))
(subpath (param "WORKING_DIR"))
(subpath (param "TMPDIR")))
;; ===== Executable mapping (dyld, Node.js JIT, Homebrew tools) =====
(allow file-map-executable
(subpath "/usr")
(subpath "/bin")
(subpath "/sbin")
(subpath "/System")
(subpath "/Library")
(subpath "/Applications")
(subpath "/opt/homebrew")
(subpath (string-append (param "HOME") "/.local/share/claude"))
(subpath (string-append (param "HOME") "/.nvm"))
(subpath (param "WORKING_DIR")))
;; ===== File reads =====
(allow file-read-data
;; Root symlinks required for path resolution
(literal "/")
(literal "/var")
(literal "/etc")
(literal "/tmp")
(literal "/private")
;; System binaries, libraries, frameworks
(subpath "/usr")
(subpath "/bin")
(subpath "/sbin")
(subpath "/System")
(subpath "/Library")
(subpath "/dev")
(subpath "/private/var")
(subpath "/private/etc")
(subpath "/private/tmp")
;; Homebrew
(subpath "/opt/homebrew")
;; Xcode / Developer tools (git, xcrun, SDKs)
(subpath "/Applications/Xcode.app")
;; Terminal app resources: terminfo (e.g. xterm-ghostty lives here, not in /usr/share/terminfo)
(subpath "/Applications/Ghostty.app/Contents/Resources")
;; Claude Code binary and past versions
(subpath (string-append (param "HOME") "/.local/share/claude"))
(subpath (string-append (param "HOME") "/.local/bin"))
;; Claude Code runtime dirs (staging files, lock files)
(subpath (string-append (param "HOME") "/.cache/claude"))
(subpath (string-append (param "HOME") "/.local/state/claude"))
;; Claude config: settings, history, sessions, plugins, skills, memory
(subpath (string-append (param "HOME") "/.claude"))
;; Node.js runtime (for tools Claude Code spawns)
(subpath (string-append (param "HOME") "/.nvm"))
;; Git config (needed for git operations in working dir)
(literal (string-append (param "HOME") "/.gitconfig"))
(subpath (string-append (param "HOME") "/.config/git"))
;; gh CLI config (authentication, hosts).
;; C2: hosts.yml contains GitHub OAuth tokens. All Claude Code subprocesses
;; inherit this read grant, not only gh itself. Scoped to ~/.config/gh (not
;; ~/.config broadly) to limit exposure to other app configs.
(subpath (string-append (param "HOME") "/.config/gh"))
;; macOS Core Foundation: user text encoding (required by CF and security frameworks)
(literal (string-append (param "HOME") "/.CFUserTextEncoding"))
;; Keychain (file read only; securityd still enforces per-item ACLs)
(subpath (string-append (param "HOME") "/Library/Keychains"))
;; Claude Code legacy flat config (checked on startup for migration)
(literal (string-append (param "HOME") "/.claude.json"))
;; Shell configs sourced when Claude Code spawns bash/zsh.
;; C1 note: these files sometimes contain exported secrets (API keys, tokens).
;; .zshrc and .bashrc are interactive-shell-only and NOT sourced by the
;; non-interactive subprocesses Claude Code spawns, so they are excluded here
;; to reduce exposure. The remaining files are sourced by login/env shells and
;; are required for correct $PATH, $NVM_DIR, etc.
(literal (string-append (param "HOME") "/.zprofile"))
(literal (string-append (param "HOME") "/.zshenv"))
(literal (string-append (param "HOME") "/.bash_profile"))
(literal (string-append (param "HOME") "/.profile"))
;; Working directory (the project being worked on)
(subpath (param "WORKING_DIR")))
;; ===== File writes (strictly limited) =====
(allow file-write*
;; Claude config: sessions, history, stats, cache, telemetry
(subpath (string-append (param "HOME") "/.claude"))
;; Claude Code runtime dirs (staging files, lock files)
(subpath (string-append (param "HOME") "/.cache/claude"))
(subpath (string-append (param "HOME") "/.local/state/claude"))
;; Legacy flat config: written on startup during settings migration.
;; Claude Code uses a lock + atomic-temp pattern for safe writes, same as
;; the keychain fix — allow the lock and temp files alongside the target.
(literal (string-append (param "HOME") "/.claude.json"))
(literal (string-append (param "HOME") "/.claude.json.backup"))
(literal (string-append (param "HOME") "/.claude.json.lock"))
(regex (string-append "^" (param "HOME") "/\\.claude\\.json\\.tmp\\.[^/]+$"))
;; Keychain: the `security` CLI uses a write-to-temp-then-rename pattern
;; when updating the keychain database. Without write access to these temp
;; files, SecKeychainItemCreateFromContent returns EPERM (silent — no deny
;; log entry), preventing OAuth token refresh from persisting and causing
;; re-login every ~1 hour. Allow only the .sb-* atomic temp files, not the
;; keychain database itself; securityd still enforces per-item ACLs.
(regex (string-append "^" (param "HOME") "/Library/Keychains/login\\.keychain-db\\.sb-[^/]+$"))
;; Working directory: source files, build artifacts, git state
(subpath (param "WORKING_DIR")))
;; Credential-store lockdown — explicit no-log denies so intent is documented
;; and these paths stay blocked even if a future broad allow accidentally overlaps.
(deny file-read* (with no-log)
(subpath (string-append (param "HOME") "/.ssh"))
(subpath (string-append (param "HOME") "/.aws"))
(subpath (string-append (param "HOME") "/.gnupg"))
(subpath (string-append (param "HOME") "/.kube")))
;; Browser credential-store lockdown.
;; These dirs contain Login Data (saved passwords), Cookies, session tokens, and
;; sync tokens. Blocked by default deny already for file-read-data, but made
;; explicit here because:
;; (a) the file-read-xattr block has a broad (subpath ~/Library) allow that
;; would otherwise permit xattr reads on these dirs, and
;; (b) a future ~/Library file-read-data widening would silently expose them.
;; file-read* covers file-read-data, file-read-metadata, and file-read-xattr.
;; Observed in deny log: Claude Code (2.1.153) probed Chrome, Brave, Arc, Edge,
;; Chromium, Vivaldi, Opera in one session. Firefox, 1Password, Bitwarden added proactively.
(deny file-read* (with no-log)
(subpath (string-append (param "HOME") "/Library/Application Support/Google/Chrome"))
(subpath (string-append (param "HOME") "/Library/Application Support/BraveSoftware"))
(subpath (string-append (param "HOME") "/Library/Application Support/Arc"))
(subpath (string-append (param "HOME") "/Library/Application Support/Microsoft Edge"))
(subpath (string-append (param "HOME") "/Library/Application Support/Chromium"))
(subpath (string-append (param "HOME") "/Library/Application Support/Vivaldi"))
(subpath (string-append (param "HOME") "/Library/Application Support/com.operasoftware.Opera"))
(subpath (string-append (param "HOME") "/Library/Application Support/Firefox"))
(subpath (string-append (param "HOME") "/Library/Application Support/1Password"))
(subpath (string-append (param "HOME") "/Library/Application Support/1Password 7"))
(subpath (string-append (param "HOME") "/Library/Application Support/Bitwarden"))
;; 1Password 8 stores vault data in Group Containers, not Application Support.
;; The file-read-xattr ~/Library allow covers ~/Library/Group Containers too.
(subpath (string-append (param "HOME") "/Library/Group Containers/2BUA8C4S2C.com.agilebits")))
;; Project write hardening (credit: github.com/CJHwong/agent-seatbelt)
;; WORKING_DIR write is intentionally broad, so deny sub-paths that can execute
;; code or reconfigure tooling outside the sandbox. .git/hooks is the critical
;; one: hooks written inside the sandbox run outside it on every git operation.
(deny file-write*
(subpath (string-append (param "WORKING_DIR") "/.git/hooks"))
(literal (string-append (param "WORKING_DIR") "/.git/config"))
(literal (string-append (param "WORKING_DIR") "/.mcp.json"))
(subpath (string-append (param "WORKING_DIR") "/.vscode"))
(subpath (string-append (param "WORKING_DIR") "/.idea")))
(allow file-write*
;; Temp directories (build tools, npm, pip, etc.)
;; M1: Use the parameterized TMPDIR (resolved canonical path) rather than
;; all of /private/var/folders, which spans every app's temp tree and could
;; allow overwriting credential files written by other processes.
(subpath "/private/tmp")
(subpath "/tmp")
(subpath (param "TMPDIR"))
;; Device nodes: null, tty, ptmx/pts (Node.js needs ptmx to spawn
;; subprocesses with a pty; without it the event loop hangs silently),
;; and dtracehelper (written by security/CF frameworks on startup)
(literal "/dev/null")
(literal "/dev/tty")
(literal "/dev/ptmx")
(literal "/dev/dtracehelper")
(subpath "/dev/pts"))
;; ===== TTY / pty ioctls =====
;; Suppress dtracehelper ioctl noise: every sh/bash/git/security subprocess
;; attempts DTRACEHIOC_ADD at startup. The deny is correct but generates
;; ~2400+ log entries per session, masking real deny events.
(deny file-ioctl (with no-log)
(literal "/dev/dtracehelper"))
;; tcgetattr/tcsetattr (raw mode), TIOCGWINSZ (terminal size), TIOCGETD, etc.
;; Without this, TIOCGETD returns EPERM (not logged by sandbox), the terminal
;; stays in cooked mode, and Ink's terminal capability queries get echoed as literal text.
;;
;; Intentionally NOT (subpath "/dev"): that would also grant ioctl on /dev/disk* and
;; /dev/bpf*, which are readable under file-read-data above. Disk ioctls (DKIOCGETBLOCKCOUNT,
;; DKIOCGETMEDIAUUID) and BPF ioctls (BIOCSETIF) are unnecessary here. Enumerate only the
;; terminal device nodes that actually need it.
(allow file-ioctl
(literal "/dev/tty")
(literal "/dev/ptmx")
(subpath "/dev/pts")
(regex "^/dev/ttys[0-9]+$"))
;; ===== Network =====
;; DNS (mDNSResponder Unix socket)
(allow network-outbound
(literal "/private/var/run/mDNSResponder"))
;; Outbound TCP: Anthropic API, GitHub, npm, OTLP telemetry
;; NOTE: Seatbelt network filters only accept * or localhost as the host — arbitrary
;; IPs and CIDR ranges are rejected at profile compile time. LAN restriction to specific
;; cluster IPs is not achievable here; use pf(4) if LAN isolation is required.
(allow network-outbound
(remote tcp))
;; Local binding for dev servers started by Claude Code.
;; L3 accepted risk: unrestricted local bind/inbound means the sandboxed process
;; could set up a listener on any port. This is intentional for dev-server support.
(allow network-bind
(local tcp "*:*"))
(allow network-inbound
(local tcp "*:*"))
;; System socket creation (needed for TCP)
(allow system-socket)
;; ===== IPC =====
;; Mach services: required for dyld, system services, XPC.
;; H1 known limitation: (allow mach-lookup) without a service-name filter lets the
;; sandboxed process reach com.apple.SecurityServer/securityd via XPC. That means
;; `security` CLI or Security.framework can still query keychain items even though
;; ~/Library/Keychains is file-read-only. Narrowing this to a specific allowlist
;; breaks TLS certificate validation (Security.framework → securityd is on the hot
;; path for every HTTPS request), so the tradeoff is accepted. Rely on per-item
;; keychain ACLs to protect sensitive secrets.
(allow mach-lookup)
(allow mach-task-name)
(allow ipc-posix-shm*)
;; ===== Misc =====
;; User defaults (terminal preferences, locale)
(allow user-preference-read)
;; JIT compilation in bundled Node.js runtime
(allow dynamic-code-generation)