diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e3ff7505c70..6d8252f86c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -408,12 +408,13 @@ On macOS, `gemini` uses Seatbelt (`sandbox-exec`) under a `permissive-open` profile (see `packages/cli/src/utils/sandbox-macos-permissive-open.sb`) that restricts writes to the project folder but otherwise allows all other operations and outbound network traffic ("open") by default. You can switch to a -`restrictive-closed` profile (see -`packages/cli/src/utils/sandbox-macos-restrictive-closed.sb`) that declines all -operations and outbound network traffic ("closed") by default by setting -`SEATBELT_PROFILE=restrictive-closed` in your environment or `.env` file. -Available built-in profiles are `{permissive,restrictive}-{open,closed,proxied}` -(see below for proxied networking). You can also switch to a custom profile +`strict-open` profile (see +`packages/cli/src/utils/sandbox-macos-strict-open.sb`) that restricts both reads +and writes to the working directory while allowing outbound network traffic by +setting `SEATBELT_PROFILE=strict-open` in your environment or `.env` file. +Available built-in profiles are `permissive-{open,proxied}`, +`restrictive-{open,proxied}`, and `strict-{open,proxied}` (see below for proxied +networking). You can also switch to a custom profile `SEATBELT_PROFILE=` if you also create a file `.gemini/sandbox-macos-.sb` under your project settings directory `.gemini`. diff --git a/docs/cli/sandbox.md b/docs/cli/sandbox.md index 28b54851c2b..9f632693c7b 100644 --- a/docs/cli/sandbox.md +++ b/docs/cli/sandbox.md @@ -82,10 +82,11 @@ gemini -p "run the test suite" Built-in profiles (set via `SEATBELT_PROFILE` env var): - `permissive-open` (default): Write restrictions, network allowed -- `permissive-closed`: Write restrictions, no network - `permissive-proxied`: Write restrictions, network via proxy - `restrictive-open`: Strict restrictions, network allowed -- `restrictive-closed`: Maximum restrictions +- `restrictive-proxied`: Strict restrictions, network via proxy +- `strict-open`: Read and write restrictions, network allowed +- `strict-proxied`: Read and write restrictions, network via proxy ### Custom sandbox flags diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index 619dbf6869f..050d6bea417 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -1262,7 +1262,10 @@ the `advanced.excludedEnvVars` setting in your `settings.json` file. few other folders, see `packages/cli/src/utils/sandbox-macos-permissive-open.sb`) but allows other operations. - - `strict`: Uses a strict profile that declines operations by default. + - `restrictive-open`: Declines operations by default, allows network. + - `strict-open`: Restricts both reads and writes to the working directory, + allows network. + - `strict-proxied`: Same as `strict-open` but routes network through proxy. - ``: Uses a custom profile. To define a custom profile, create a file named `sandbox-macos-.sb` in your project's `.gemini/` directory (e.g., `my-project/.gemini/sandbox-macos-custom.sb`). diff --git a/packages/cli/src/utils/sandbox-macos-permissive-closed.sb b/packages/cli/src/utils/sandbox-macos-permissive-closed.sb deleted file mode 100644 index cf64da94507..00000000000 --- a/packages/cli/src/utils/sandbox-macos-permissive-closed.sb +++ /dev/null @@ -1,32 +0,0 @@ -(version 1) - -;; allow everything by default -(allow default) - -;; deny all writes EXCEPT under specific paths -(deny file-write*) -(allow file-write* - (subpath (param "TARGET_DIR")) - (subpath (param "TMP_DIR")) - (subpath (param "CACHE_DIR")) - (subpath (string-append (param "HOME_DIR") "/.gemini")) - (subpath (string-append (param "HOME_DIR") "/.npm")) - (subpath (string-append (param "HOME_DIR") "/.cache")) - (subpath (string-append (param "HOME_DIR") "/.gitconfig")) - ;; Allow writes to included directories from --include-directories - (subpath (param "INCLUDE_DIR_0")) - (subpath (param "INCLUDE_DIR_1")) - (subpath (param "INCLUDE_DIR_2")) - (subpath (param "INCLUDE_DIR_3")) - (subpath (param "INCLUDE_DIR_4")) - (literal "/dev/stdout") - (literal "/dev/stderr") - (literal "/dev/null") -) - -;; deny all inbound network traffic EXCEPT on debugger port -(deny network-inbound) -(allow network-inbound (local ip "localhost:9229")) - -;; deny all outbound network traffic -(deny network-outbound) diff --git a/packages/cli/src/utils/sandbox-macos-restrictive-closed.sb b/packages/cli/src/utils/sandbox-macos-strict-open.sb similarity index 64% rename from packages/cli/src/utils/sandbox-macos-restrictive-closed.sb rename to packages/cli/src/utils/sandbox-macos-strict-open.sb index 17d0c073202..e5cbd1b3341 100644 --- a/packages/cli/src/utils/sandbox-macos-restrictive-closed.sb +++ b/packages/cli/src/utils/sandbox-macos-strict-open.sb @@ -3,8 +3,43 @@ ;; deny everything by default (deny default) -;; allow reading files from anywhere on host -(allow file-read*) +;; allow reading ONLY from working directory, system paths, and essential user paths +(allow file-read* + (literal "/") + (subpath (param "TARGET_DIR")) + (subpath (param "TMP_DIR")) + (subpath (param "CACHE_DIR")) + ;; Only allow reading essential dotfiles/directories under HOME, not the entire HOME + (subpath (string-append (param "HOME_DIR") "/.gemini")) + (subpath (string-append (param "HOME_DIR") "/.npm")) + (subpath (string-append (param "HOME_DIR") "/.cache")) + (literal (string-append (param "HOME_DIR") "/.gitconfig")) + (subpath (string-append (param "HOME_DIR") "/.nvm")) + (subpath (string-append (param "HOME_DIR") "/.fnm")) + (subpath (string-append (param "HOME_DIR") "/.node")) + (subpath (string-append (param "HOME_DIR") "/.config")) + ;; Allow reads from included directories + (subpath (param "INCLUDE_DIR_0")) + (subpath (param "INCLUDE_DIR_1")) + (subpath (param "INCLUDE_DIR_2")) + (subpath (param "INCLUDE_DIR_3")) + (subpath (param "INCLUDE_DIR_4")) + ;; System paths required for Node.js, shell, and common tools + (subpath "/usr") + (subpath "/bin") + (subpath "/sbin") + (subpath "/Library") + (subpath "/System") + (subpath "/private") + (subpath "/dev") + (subpath "/etc") + (subpath "/opt") + (subpath "/Applications") +) + +;; allow path traversal everywhere (metadata only: stat/lstat, NOT readdir or file content) +;; this is needed for Node.js module resolution to traverse intermediate directories +(allow file-read-metadata) ;; allow exec/fork (children inherit policy) (allow process-exec) @@ -70,7 +105,7 @@ (subpath (string-append (param "HOME_DIR") "/.gemini")) (subpath (string-append (param "HOME_DIR") "/.npm")) (subpath (string-append (param "HOME_DIR") "/.cache")) - (subpath (string-append (param "HOME_DIR") "/.gitconfig")) + (literal (string-append (param "HOME_DIR") "/.gitconfig")) ;; Allow writes to included directories from --include-directories (subpath (param "INCLUDE_DIR_0")) (subpath (param "INCLUDE_DIR_1")) @@ -90,4 +125,7 @@ (allow file-ioctl (regex #"^/dev/tty.*")) ;; allow inbound network traffic on debugger port -(allow network-inbound (local ip "localhost:9229")) \ No newline at end of file +(allow network-inbound (local ip "localhost:9229")) + +;; allow all outbound network traffic +(allow network-outbound) diff --git a/packages/cli/src/utils/sandbox-macos-strict-proxied.sb b/packages/cli/src/utils/sandbox-macos-strict-proxied.sb new file mode 100644 index 00000000000..53b102f43ef --- /dev/null +++ b/packages/cli/src/utils/sandbox-macos-strict-proxied.sb @@ -0,0 +1,133 @@ +(version 1) + +;; deny everything by default +(deny default) + +;; allow reading ONLY from working directory, system paths, and essential user paths +(allow file-read* + (literal "/") + (subpath (param "TARGET_DIR")) + (subpath (param "TMP_DIR")) + (subpath (param "CACHE_DIR")) + ;; Only allow reading essential dotfiles/directories under HOME, not the entire HOME + (subpath (string-append (param "HOME_DIR") "/.gemini")) + (subpath (string-append (param "HOME_DIR") "/.npm")) + (subpath (string-append (param "HOME_DIR") "/.cache")) + (literal (string-append (param "HOME_DIR") "/.gitconfig")) + (subpath (string-append (param "HOME_DIR") "/.nvm")) + (subpath (string-append (param "HOME_DIR") "/.fnm")) + (subpath (string-append (param "HOME_DIR") "/.node")) + (subpath (string-append (param "HOME_DIR") "/.config")) + ;; Allow reads from included directories + (subpath (param "INCLUDE_DIR_0")) + (subpath (param "INCLUDE_DIR_1")) + (subpath (param "INCLUDE_DIR_2")) + (subpath (param "INCLUDE_DIR_3")) + (subpath (param "INCLUDE_DIR_4")) + ;; System paths required for Node.js, shell, and common tools + (subpath "/usr") + (subpath "/bin") + (subpath "/sbin") + (subpath "/Library") + (subpath "/System") + (subpath "/private") + (subpath "/dev") + (subpath "/etc") + (subpath "/opt") + (subpath "/Applications") +) + +;; allow path traversal everywhere (metadata only: stat/lstat, NOT readdir or file content) +;; this is needed for Node.js module resolution to traverse intermediate directories +(allow file-read-metadata) + +;; allow exec/fork (children inherit policy) +(allow process-exec) +(allow process-fork) + +;; allow signals to self, e.g. SIGPIPE on write to closed pipe +(allow signal (target self)) + +;; allow read access to specific information about system +;; from https://source.chromium.org/chromium/chromium/src/+/main:sandbox/policy/mac/common.sb;l=273-319;drc=7b3962fe2e5fc9e2ee58000dc8fbf3429d84d3bd +(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.ncpu") + (sysctl-name "hw.nperflevels") + (sysctl-name "hw.optional.arm.FEAT_BF16") + (sysctl-name "hw.optional.arm.FEAT_DotProd") + (sysctl-name "hw.optional.arm.FEAT_FCMA") + (sysctl-name "hw.optional.arm.FEAT_FHM") + (sysctl-name "hw.optional.arm.FEAT_FP16") + (sysctl-name "hw.optional.arm.FEAT_I8MM") + (sysctl-name "hw.optional.arm.FEAT_JSCVT") + (sysctl-name "hw.optional.arm.FEAT_LSE") + (sysctl-name "hw.optional.arm.FEAT_RDM") + (sysctl-name "hw.optional.arm.FEAT_SHA512") + (sysctl-name "hw.optional.armv8_2_sha512") + (sysctl-name "hw.packages") + (sysctl-name "hw.pagesize_compat") + (sysctl-name "hw.physicalcpu_max") + (sysctl-name "hw.tbfrequency_compat") + (sysctl-name "hw.vectorunit") + (sysctl-name "kern.hostname") + (sysctl-name "kern.maxfilesperproc") + (sysctl-name "kern.osproductversion") + (sysctl-name "kern.osrelease") + (sysctl-name "kern.ostype") + (sysctl-name "kern.osvariant_status") + (sysctl-name "kern.osversion") + (sysctl-name "kern.secure_kernel") + (sysctl-name "kern.usrstack64") + (sysctl-name "kern.version") + (sysctl-name "sysctl.proc_cputype") + (sysctl-name-prefix "hw.perflevel") +) + +;; allow writes to specific paths +(allow file-write* + (subpath (param "TARGET_DIR")) + (subpath (param "TMP_DIR")) + (subpath (param "CACHE_DIR")) + (subpath (string-append (param "HOME_DIR") "/.gemini")) + (subpath (string-append (param "HOME_DIR") "/.npm")) + (subpath (string-append (param "HOME_DIR") "/.cache")) + (literal (string-append (param "HOME_DIR") "/.gitconfig")) + ;; Allow writes to included directories from --include-directories + (subpath (param "INCLUDE_DIR_0")) + (subpath (param "INCLUDE_DIR_1")) + (subpath (param "INCLUDE_DIR_2")) + (subpath (param "INCLUDE_DIR_3")) + (subpath (param "INCLUDE_DIR_4")) + (literal "/dev/stdout") + (literal "/dev/stderr") + (literal "/dev/null") +) + +;; allow communication with sysmond for process listing (e.g. for pgrep) +(allow mach-lookup (global-name "com.apple.sysmond")) + +;; enable terminal access required by ink +;; fixes setRawMode EPERM failure (at node:tty:81:24) +(allow file-ioctl (regex #"^/dev/tty.*")) + +;; allow inbound network traffic on debugger port +(allow network-inbound (local ip "localhost:9229")) + +;; allow outbound network traffic through proxy on localhost:8877 +;; set `GEMINI_SANDBOX_PROXY_COMMAND=` to run proxy alongside sandbox +;; proxy must listen on :::8877 (see docs/examples/proxy-script.md) +(allow network-outbound (remote tcp "localhost:8877")) diff --git a/packages/cli/src/utils/sandboxUtils.ts b/packages/cli/src/utils/sandboxUtils.ts index 48cfb30f000..b33a1af3a31 100644 --- a/packages/cli/src/utils/sandboxUtils.ts +++ b/packages/cli/src/utils/sandboxUtils.ts @@ -15,11 +15,11 @@ export const SANDBOX_NETWORK_NAME = 'gemini-cli-sandbox'; export const SANDBOX_PROXY_NAME = 'gemini-cli-sandbox-proxy'; export const BUILTIN_SEATBELT_PROFILES = [ 'permissive-open', - 'permissive-closed', 'permissive-proxied', 'restrictive-open', - 'restrictive-closed', 'restrictive-proxied', + 'strict-open', + 'strict-proxied', ]; export function getContainerPath(hostPath: string): string { diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 45a3a953b5c..5e6995a76d7 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -1335,7 +1335,8 @@ export class Config { !!sandboxConfig && sandboxConfig.command === 'sandbox-exec' && !!seatbeltProfile && - seatbeltProfile.startsWith('restrictive-') + (seatbeltProfile.startsWith('restrictive-') || + seatbeltProfile.startsWith('strict-')) ); }