summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hostnix/mojo/claude.sb296
-rw-r--r--hostnix/mojo/flake.nix10
2 files changed, 305 insertions, 1 deletions
diff --git a/hostnix/mojo/claude.sb b/hostnix/mojo/claude.sb
new file mode 100644
index 0000000..72797de
--- /dev/null
+++ b/hostnix/mojo/claude.sb
@@ -0,0 +1,296 @@
+(version 1)
+
+;; Based on Para Sandboxing Profile - Standard - https://github.com/2mawi2/para/blob/218259b6e260be43334f308a74108f31920f7ca4/src/core/sandbox/profiles/standard.sb
+;; Forbids reading HOME_DIR except for cwd (TARGET_DIR)
+;; Forbids writing other than to cwd (TARGET_DIR)
+;; All network is allowed
+
+;; Deny everything by default
+(deny default)
+
+;; Allow network access (required for Claude API)
+(allow network*)
+
+;; Deny reading files anywhere on host (allow rules override this below)
+(deny file-read*)
+
+(deny file-read*
+ (subpath "/")
+ (subpath "/Users")
+ (subpath (param "HOME_DIR"))
+ (subpath (string-append (param "HOME_DIR") "/.ssh"))
+)
+
+;; allow directories required to launch claude-code
+(allow file-read*
+ (subpath "/usr")
+ (subpath "/bin")
+ (subpath "/opt")
+ (subpath "/var")
+ (subpath "/private/var")
+ (subpath "/etc")
+ (subpath "/private/etc")
+ (subpath "/System")
+ (subpath "/nix")
+ )
+
+;; necessary for nix-darwin's `/run/current-system/sw/bin`
+(allow file-read-metadata
+ (subpath "/run"))
+
+;; === IMPORTANT === MODIFY this section to include ALL directories leading to claude workdir ===
+;; for some reason claude-code needs list access to all parent directories of TARGET_DIR
+;; - it doesn't need access to read the contents of directories, only the directories
+;; themselves. Otherwise it will set PATH to "" and disable colored output
+(allow file-read*
+ (literal "/")
+ )
+
+(allow file-read*
+ ;; Git configuration (for commits)
+ (subpath (string-append (param "HOME_DIR") "/.config/git"))
+ (subpath (string-append (param "HOME_DIR") "/.config/jj"))
+ (literal (string-append (param "HOME_DIR") "/.gitconfig"))
+ ;; Nix configuration
+ (subpath (string-append (param "HOME_DIR") "/.config/nix"))
+ (subpath (string-append (param "HOME_DIR") "/.local/share/nix"))
+ ;; Nix profile binaries (symlinks to /nix/store)
+ (subpath (string-append (param "HOME_DIR") "/.nix-profile"))
+ (subpath (string-append (param "HOME_DIR") "/.local/state/nix"))
+ ;; gh CLI
+ (subpath (string-append (param "HOME_DIR") "/.config/gh"))
+)
+
+;; Allow process execution and forking (children inherit policy)
+(allow process-exec)
+(allow process-fork)
+;; Essential permissions - based on Chrome sandbox policy
+;; Process permissions - from https://github.com/anthropic-experimental/sandbox-runtime/blob/1bafa66a2c3ebc52569fc0c1a868e85e778f66a0/src/sandbox/macos-sandbox-utils.ts#L200
+(allow process-info* (target same-sandbox))
+;; Allow signals to all children
+(allow signal (target same-sandbox))
+(allow mach-priv-task-port (target same-sandbox))
+
+;; User preferences - from https://github.com/anthropic-experimental/sandbox-runtime/blob/1bafa66a2c3ebc52569fc0c1a868e85e778f66a0/src/sandbox/macos-sandbox-utils.ts#L200
+;; (allow user-preference-read) ;; doesn't seem to be required by claude-code
+
+;; Allow read access to system information
+;; From Chromium's sandbox policy for macOS
+(allow sysctl-read
+ (sysctl-name "hw.activecpu")
+ (sysctl-name "hw.busfrequency_compat")
+ (sysctl-name "hw.byteorder")
+ (sysctl-name "hw.cacheconfig")
+ (sysctl-name "hw.cachelinesize_compat")
+ (sysctl-name "hw.cpufamily")
+ (sysctl-name "hw.cpufrequency_compat")
+ (sysctl-name "hw.cputype")
+ (sysctl-name "hw.l1dcachesize_compat")
+ (sysctl-name "hw.l1icachesize_compat")
+ (sysctl-name "hw.l2cachesize_compat")
+ (sysctl-name "hw.l3cachesize_compat")
+ (sysctl-name "hw.logicalcpu_max")
+ (sysctl-name "hw.machine")
+ (sysctl-name "hw.memsize")
+ (sysctl-name "hw.ncpu")
+ ;; Needed for Lix
+ (sysctl-name "hw.pagesize")
+ (sysctl-name "hw.pagesize_compat")
+ (sysctl-name "hw.physicalcpu_max")
+ (sysctl-name "hw.tbfrequency_compat")
+ (sysctl-name "kern.hostname")
+ (sysctl-name "kern.maxfilesperproc")
+ (sysctl-name "kern.osproductversion")
+ (sysctl-name "kern.osrelease")
+ (sysctl-name "kern.ostype")
+ (sysctl-name "kern.osversion")
+ (sysctl-name "kern.secure_kernel")
+ (sysctl-name "kern.version")
+)
+
+;; Allow file writes to specific paths only
+;; Note: file-write* does NOT include file-write-create, so we need both
+(allow file-read* file-write* file-write-create file-read-metadata file-ioctl
+ ;; Project directory - primary workspace
+ (subpath (param "TARGET_DIR"))
+
+ ;; Temporary directories
+ (subpath (param "TMP_DIR"))
+ (subpath "/tmp")
+ (subpath "/private/tmp")
+ (subpath "/var/folders") ; macOS temp directory root
+ (subpath "/private/var/folders") ; Real path (var is symlink to private/var)
+
+ ;; below is from `para`'s' sandbox.sb, but these rules don't work because sandbox-exec uses GLOB 'regexes'
+ ;; (regex #"^/var/folders/[^/]+/[^/]+/[^/]+/.*") ; macOS temp dirs and subdirs
+ ;; (regex #"^/private/var/folders/[^/]+/[^/]+/[^/]+/.*") ; Real path version
+ ;; (regex #"^/var/folders/.*") ; Allow all subdirectories under /var/folders for broader compatibility
+ ;; (regex #"^/private/var/folders/.*") ; Real path version
+
+ ;; Cache directory
+ (subpath (param "CACHE_DIR"))
+ (subpath (string-append (param "HOME_DIR") "/.cache"))
+
+ ;; Claude configuration
+ (subpath (string-append (param "HOME_DIR") "/.claude"))
+ (literal (string-append (param "HOME_DIR") "/.claude.json"))
+ (literal (string-append (param "HOME_DIR") "/.claude.json.backup"))
+ (subpath (string-append (param "HOME_DIR") "/Library/Caches/claude-cli-nodejs"))
+
+ ;; Gemini configuration
+ (subpath (string-append (param "HOME_DIR") "/.gemini"))
+
+ ;; Standard I/O devices
+ (literal "/dev/stdout")
+ (literal "/dev/stderr")
+ (literal "/dev/null")
+ (literal "/dev/zero")
+ (literal "/dev/tty")
+ (literal "/dev/ptmx")
+ (literal "/dev/urandom")
+ (literal "/dev/random")
+ (regex #"^/dev/tty*")
+ (regex #"^/dev/pty*")
+)
+
+;; File I/O on device files - sandbox-runtime - https://github.com/anthropic-experimental/sandbox-runtime/blob/1bafa66a2c3ebc52569fc0c1a868e85e778f66a0/src/sandbox/macos-sandbox-utils.ts#L200
+(allow file-ioctl file-read-metadata file-read-data file-write-data (literal "/dev/dtracehelper"))
+(allow file-ioctl file-read-metadata file-read-data file-write-data
+ (require-all
+ (literal "/dev/null")
+ (vnode-type CHARACTER-DEVICE)
+ )
+)
+
+;; Gemini-specific permissions
+(allow pseudo-tty)
+
+;; Allow mach lookups for essential services
+(allow mach-lookup
+ (global-name "com.apple.sysmond") ; For process listing
+ (global-name "com.apple.FSEvents") ; For Node.js file watching
+ (global-name "com.apple.SystemConfiguration.DNSConfiguration") ; For DNS resolution in Lix
+)
+
+;; Allow file attribute operations needed for file creation
+;; (allow file-write-setugid)
+;; (allow file-write-mode)
+;; (allow file-write-owner)
+;; (allow file-write-times)
+;; (allow file-write-flags)
+
+(allow mach-lookup
+ (global-name "com.apple.audio.systemsoundserver")
+ (global-name "com.apple.distributed_notifications@Uv3")
+ (global-name "com.apple.FontObjectsServer")
+ (global-name "com.apple.fonts")
+ (global-name "com.apple.logd")
+ (global-name "com.apple.lsd.mapdb")
+ (global-name "com.apple.PowerManagement.control")
+ (global-name "com.apple.system.logger")
+ (global-name "com.apple.system.notification_center")
+ (global-name "com.apple.trustd.agent")
+ (global-name "com.apple.system.opendirectoryd.libinfo")
+ (global-name "com.apple.system.opendirectoryd.membership")
+ (global-name "com.apple.bsd.dirhelper")
+ (global-name "com.apple.securityd.xpc")
+ (global-name "com.apple.coreservices.launchservicesd")
+)
+
+;; The following is required to get Claude API Key from macOS Keychain (if logged in via /login)
+
+;; Specifically allow login keychain
+(allow file-read*
+ (literal (string-append (param "HOME_DIR") "/Library/Keychains/login.keychain-db"))
+)
+
+;; Critical: Allow communication with securityd (keychain daemon)
+(allow mach-lookup
+ (global-name "com.apple.SecurityServer")
+ (global-name "com.apple.securityd")
+ (global-name "com.apple.securityd.xpc")
+)
+
+;; Java/Scala development permissions
+
+;; Java-specific services
+(allow mach-lookup
+ (global-name "com.apple.diagnosticd")
+ (global-name "com.apple.SystemConfiguration.configd")
+)
+
+;; Java-specific sysctl reads
+(allow sysctl-read
+ (sysctl-name "security.mac.lockdown_mode_state")
+ (sysctl-name "kern.bootargs")
+ (sysctl-name "kern.osvariant_status")
+ (sysctl-name "kern.argmax")
+ (sysctl-name "hw.ephemeral_storage")
+ (sysctl-name "hw.optional.armv8_crc32")
+ (sysctl-name "hw.optional.arm.FEAT_LSE")
+ (sysctl-name "hw.optional.armv8_1_atomics")
+ (sysctl-name "hw.optional.arm.FEAT_SHA512")
+ (sysctl-name "hw.optional.armv8_2_sha512")
+ (sysctl-name "hw.optional.arm.FEAT_SHA3")
+ (sysctl-name "hw.optional.armv8_2_sha3")
+ (sysctl-name "net.routetable.0.0.3.0")
+)
+
+;; Java needs to read dtracehelper
+(allow file-read-data (literal "/dev/dtracehelper"))
+
+;: ;; Java needs IPC shared memory for notification center
+;; (allow ipc-posix-shm-read-data
+;; (ipc-posix-name "apple.shm.notification_center")
+;; )
+
+;; Java needs system sockets (domain:32 is AF_NDRV for network device raw access)
+(allow system-socket)
+
+;; Java needs to read metadata on certain directories
+(allow file-read-metadata
+ (literal "/dev")
+ (literal "/private")
+ (literal "/Library")
+ (literal (string-append (param "HOME_DIR") "/Library"))
+ (literal (string-append (param "HOME_DIR") "/Library/Caches"))
+)
+
+(allow file-read-data
+ (literal "/dev")
+)
+
+;; Java needs to read various preference files
+(allow file-read*
+ (literal "/Library/Preferences/Logging/com.apple.diagnosticd.filter.plist")
+ (literal "/Library/Preferences/.GlobalPreferences.plist")
+ (literal "/Library/Preferences/com.apple.networkd.plist")
+ (literal (string-append (param "HOME_DIR") "/Library/Preferences/.GlobalPreferences.plist"))
+ (literal (string-append (param "HOME_DIR") "/Library/Preferences/.GlobalPreferences_m.plist"))
+ (regex (string-append "^" (param "HOME_DIR") "/Library/Preferences/ByHost/\\.GlobalPreferences\\..*\\.plist$"))
+)
+
+;; read/write ~/.sbt, coursier and bloop caches
+(allow file-read* file-write*
+ (subpath (string-append (param "HOME_DIR") "/.sbt"))
+ (subpath (string-append (param "HOME_DIR") "/.ivy2"))
+ (subpath (string-append (param "HOME_DIR") "/.m2"))
+ (subpath (string-append (param "HOME_DIR") "/.jgit"))
+ (subpath (string-append (param "HOME_DIR") "/.config/jgit"))
+ (subpath (string-append (param "HOME_DIR") "/Library/Caches/Coursier"))
+ (subpath (string-append (param "HOME_DIR") "/Library/Caches/ScalaCli"))
+)
+
+;; read [~]/Library/Java
+(allow file-read*
+ (subpath (string-append (param "HOME_DIR") "/Library/Java"))
+ (subpath "/Library/Java")
+)
+
+;; Generated allow-read rules for: /Users/joe/src
+;; for some reason claude-code needs list access to all parent directories of TARGET_DIR
+;; - it doesn't need access to read the contents of directories, only the directories
+;; themselves. Otherwise it will set PATH to "" and disable colored output
+(allow file-read* (literal "/Users"))
+(allow file-read* (literal "/Users/joe"))
+(allow file-read* (literal "/Users/joe/src"))
diff --git a/hostnix/mojo/flake.nix b/hostnix/mojo/flake.nix
index 87e3c8d..78584e4 100644
--- a/hostnix/mojo/flake.nix
+++ b/hostnix/mojo/flake.nix
@@ -19,7 +19,15 @@
};
in
[
- pkgsUnstable.claude-code
+ (pkgs.writeShellScriptBin "claude" ''
+ exec /usr/bin/sandbox-exec -f ${self}/claude.sb \
+ -D TARGET_DIR=$HOME/src \
+ -D TMP_DIR=/tmp \
+ -D HOME_DIR=$HOME \
+ -D CACHE_DIR=$HOME/.cache \
+ ${pkgsUnstable.claude-code}/bin/claude \
+ --allow-dangerously-skip-permissions "$@"
+ '')
pkgsUnstable.jujutsu
];