| import { accessSync, constants } from 'node:fs'; |
| import { spawnSync } from 'node:child_process'; |
| |
| import type { SandboxPlatform } from './types.js'; |
| |
| export type LinuxSandboxCapability = |
| | { available: true; bwrapPath: string } |
| | { |
| available: false; |
| reason: 'non-linux' | 'missing-bwrap' | 'probe-failed'; |
| bwrapPath?: string; |
| detail?: string; |
| }; |
| |
| export interface DetectLinuxSandboxCapabilityInput { |
| platform?: SandboxPlatform; |
| bwrapPath?: string; |
| } |
| |
| export const LINUX_BWRAP_REQUIRED_OPTIONS = ['--seccomp'] as const; |
| |
| export const LINUX_BWRAP_REQUIRED_NAMESPACE_ARGS = [ |
| '--unshare-user', |
| '--unshare-pid', |
| '--unshare-ipc', |
| '--unshare-uts', |
| '--unshare-cgroup', |
| ] as const; |
| |
| export const LINUX_BWRAP_PROBE_ARGS = [ |
| '--die-with-parent', |
| '--new-session', |
| ...LINUX_BWRAP_REQUIRED_NAMESPACE_ARGS, |
| '--unshare-net', |
| '--ro-bind', |
| '/', |
| '/', |
| '--proc', |
| '/proc', |
| '--dev', |
| '/dev', |
| '--', |
| '/bin/true', |
| ] as const; |
| |
| export function detectLinuxSandboxCapability( |
| input: DetectLinuxSandboxCapabilityInput = {}, |
| ): LinuxSandboxCapability { |
| const platform = input.platform ?? process.platform; |
| if (platform !== 'linux') return { available: false, reason: 'non-linux' }; |
| |
| const bwrapPath = input.bwrapPath ?? '/usr/bin/bwrap'; |
| try { |
| accessSync(bwrapPath, constants.X_OK); |
| } catch { |
| return { available: false, reason: 'missing-bwrap', bwrapPath }; |
| } |
| |
| const help = spawnSync(bwrapPath, ['--help'], { |
| encoding: 'utf8', |
| timeout: 5_000, |
| windowsHide: true, |
| }); |
| const helpText = `${help.stdout ?? ''}\n${help.stderr ?? ''}`; |
| if ( |
| help.status !== 0 || |
| LINUX_BWRAP_REQUIRED_OPTIONS.some((option) => !helpText.includes(option)) |
| ) { |
| return { |
| available: false, |
| reason: 'probe-failed', |
| bwrapPath, |
| detail: 'bubblewrap does not advertise required seccomp support', |
| }; |
| } |
| |
| const probe = spawnSync(bwrapPath, [...LINUX_BWRAP_PROBE_ARGS], { |
| encoding: 'utf8', |
| timeout: 5_000, |
| windowsHide: true, |
| }); |
| if (probe.status !== 0) { |
| const detail = |
| probe.error?.message || probe.stderr.trim() || `exit ${probe.status ?? 'unknown'}`; |
| return { available: false, reason: 'probe-failed', bwrapPath, detail }; |
| } |
| return { available: true, bwrapPath }; |
| } |