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)

This commit is contained in:
Olcan 2025-05-07 20:03:29 -07:00 committed by GitHub
parent 34fe142894
commit d524309e3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 133 additions and 3 deletions

View File

@ -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>

View File

@ -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")
)

View File

@ -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.*"))

View File

@ -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();

View File

@ -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