summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlcan <[email protected]>2025-05-07 20:03:29 -0700
committerGitHub <[email protected]>2025-05-07 20:03:29 -0700
commitd524309e3c66a0c1f2e0a5038c204735eb81683b (patch)
tree1947fa74d9c8f7c4b0deec8972b0382f8b862a87
parent34fe142894fb5ce3f6800745a70e382684ea8101 (diff)
use seatbelt on macos, with two profiles: minimal (default) which only restricts writes, and strict, which is deny-by-default and only allows specific operations (#283)
-rw-r--r--packages/cli/src/ui/components/Footer.tsx8
-rw-r--r--packages/cli/src/utils/sandbox-macos-minimal.sb15
-rw-r--r--packages/cli/src/utils/sandbox-macos-strict.sb82
-rw-r--r--packages/cli/src/utils/sandbox.ts29
-rwxr-xr-xscripts/build_package.sh2
5 files changed, 133 insertions, 3 deletions
diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx
index b23d5b16..71a8823e 100644
--- a/packages/cli/src/ui/components/Footer.tsx
+++ b/packages/cli/src/ui/components/Footer.tsx
@@ -45,10 +45,14 @@ export const Footer: React.FC<FooterProps> = ({
justifyContent="center"
display="flex"
>
- {process.env.SANDBOX ? (
+ {process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec' ? (
<Text color="green"> {process.env.SANDBOX} </Text>
+ ) : process.env.SANDBOX === 'sandbox-exec' ? (
+ <Text color={Colors.AccentYellow}>
+ using macos seatbelt ({process.env.SANDBOX_EXEC_PROFILE})
+ </Text>
) : (
- <Text color={Colors.AccentRed}> WARNING: OUTSIDE SANDBOX </Text>
+ <Text color={Colors.AccentRed}> WARNING: SANDBOX NOT ENABLED </Text>
)}
</Box>
diff --git a/packages/cli/src/utils/sandbox-macos-minimal.sb b/packages/cli/src/utils/sandbox-macos-minimal.sb
new file mode 100644
index 00000000..c304b838
--- /dev/null
+++ b/packages/cli/src/utils/sandbox-macos-minimal.sb
@@ -0,0 +1,15 @@
+(version 1)
+
+;; allow everything by default
+(allow default)
+
+;; deny all writes EXCEPT under project directory, temp directory, stdout/stderr and /dev/null
+(deny file-write*)
+(allow file-write*
+ (subpath (param "TARGET_DIR"))
+ (subpath (param "TMP_DIR"))
+ (literal "/dev/stdout")
+ (literal "/dev/stderr")
+ (literal "/dev/null")
+)
+
diff --git a/packages/cli/src/utils/sandbox-macos-strict.sb b/packages/cli/src/utils/sandbox-macos-strict.sb
new file mode 100644
index 00000000..4c7c2df0
--- /dev/null
+++ b/packages/cli/src/utils/sandbox-macos-strict.sb
@@ -0,0 +1,82 @@
+(version 1)
+
+;; deny everything by default
+(deny default)
+
+;; allow reading files from anywhere on host
+(allow file-read*)
+
+;; 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 project directory, temp directory, stdout/stderr and /dev/null
+(allow file-write*
+ (subpath (param "TARGET_DIR"))
+ (subpath (param "TMP_DIR"))
+ (literal "/dev/stdout")
+ (literal "/dev/stderr")
+ (literal "/dev/null")
+)
+
+;; allow outbound network connections
+(allow network-outbound)
+
+;; 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.*"))
diff --git a/packages/cli/src/utils/sandbox.ts b/packages/cli/src/utils/sandbox.ts
index 508ce368..697ff7e3 100644
--- a/packages/cli/src/utils/sandbox.ts
+++ b/packages/cli/src/utils/sandbox.ts
@@ -45,6 +45,14 @@ export function sandbox_command(sandbox?: string | boolean): string {
process.exit(1);
}
} else {
+ // if we are on macOS (Darwin) and sandbox-exec is available, use that for minimal sandboxing
+ if (
+ os.platform() === 'darwin' &&
+ execSync('command -v sandbox-exec || true').toString().trim()
+ ) {
+ return 'sandbox-exec';
+ }
+
return ''; // no sandbox
}
}
@@ -133,6 +141,27 @@ function entrypoint(workdir: string): string[] {
}
export async function start_sandbox(sandbox: string) {
+ if (sandbox === 'sandbox-exec') {
+ process.env.SANDBOX_EXEC_PROFILE ??= 'minimal';
+ const args = [
+ '-D',
+ `TARGET_DIR=${process.cwd()}`,
+ '-D',
+ `TMP_DIR=${fs.realpathSync(os.tmpdir())}`,
+ '-f',
+ new URL(
+ `sandbox-macos-${process.env.SANDBOX_EXEC_PROFILE}.sb`,
+ import.meta.url,
+ ).pathname,
+ 'bash',
+ '-c',
+ 'SANDBOX=sandbox-exec ' +
+ process.argv.map((arg) => quote([arg])).join(' '),
+ ];
+ spawnSync(sandbox, args, { stdio: 'inherit' });
+ return;
+ }
+
// determine full path for gemini-code to distinguish linked vs installed setting
const gcPath = execSync(`realpath $(which gemini-code)`).toString().trim();
diff --git a/scripts/build_package.sh b/scripts/build_package.sh
index b536723f..28053a89 100755
--- a/scripts/build_package.sh
+++ b/scripts/build_package.sh
@@ -27,7 +27,7 @@ fi
tsc --build
# copy .{md,json} files (replace -q with -i to see itemized changes)
-rsync -aq --delete --include='*.md' --include='*.json' --include='*/' --exclude='*' ./src/ ./dist/src/
+rsync -aq --delete --include='*.md' --include='*.json' --include='*.sb' --include='*/' --exclude='*' ./src/ ./dist/src/
# touch dist/.last_build
touch dist/.last_build