Files
sandbox-runtime/test/sandbox/linux-dependency-error.test.ts
Dylan Conway 2dc232be92 Add seccomp argv0 mode for multicall-binary invocation (#203)
When the caller has apply-seccomp compiled into its own executable
(busybox-style multicall dispatch), there is no standalone binary on
disk to resolve. seccompConfig.argv0 lets the caller supply the dispatch
name and a verbatim applyPath (e.g. an inherited-fd path like
/proc/self/fd/N); the wrapped invocation becomes

    ARGV0=<argv0> <applyPath> <shell> -c <cmd>

and the on-disk getApplySeccompBinaryPath lookup is skipped.

- sandbox-config: optional argv0 string on SeccompConfigSchema (parallel
  to ripgrep.argv0)
- linux-sandbox-utils: resolveApplySeccompPrefix helper returns a
  shell-ready prefix for both modes; non-argv0 output is byte-identical
  to before. Dependency checks short-circuit the disk lookup when argv0
  is set. Inline {applyPath?; argv0?} shapes replaced with the
  SeccompConfig type.
- sandbox-manager: use SeccompConfig type
- tests: argv0 dependency-check short-circuit, wrapped-command prefix
  format, shell-quoting of hostile inputs, argv0-without-applyPath error
2026-04-02 12:29:16 -07:00

151 lines
4.3 KiB
TypeScript

import { describe, test, expect, beforeEach, afterEach, spyOn } from 'bun:test'
import * as which from '../../src/utils/which.js'
import * as seccomp from '../../src/sandbox/generate-seccomp-filter.js'
import {
checkLinuxDependencies,
getLinuxDependencyStatus,
} from '../../src/sandbox/linux-sandbox-utils.js'
// Spies set up in beforeEach, torn down in afterEach. Each test overrides
// just the piece it's exercising. spyOn patches the export binding, so
// linux-sandbox-utils' own imports see the replacement.
let whichSpy: ReturnType<typeof spyOn>
let applySpy: ReturnType<typeof spyOn>
beforeEach(() => {
whichSpy = spyOn(which, 'whichSync').mockImplementation(
(bin: string) => `/usr/bin/${bin}`,
)
applySpy = spyOn(seccomp, 'getApplySeccompBinaryPath').mockReturnValue(
'/path/to/apply-seccomp',
)
})
afterEach(() => {
whichSpy.mockRestore()
applySpy.mockRestore()
})
describe('checkLinuxDependencies', () => {
test('returns no errors or warnings when all dependencies present', () => {
const result = checkLinuxDependencies()
expect(result.errors).toEqual([])
expect(result.warnings).toEqual([])
})
test('returns error when bwrap missing', () => {
whichSpy.mockImplementation((bin: string) =>
bin === 'bwrap' ? null : `/usr/bin/${bin}`,
)
const result = checkLinuxDependencies()
expect(result.errors).toContain('bubblewrap (bwrap) not installed')
expect(result.errors.length).toBe(1)
})
test('returns error when socat missing', () => {
whichSpy.mockImplementation((bin: string) =>
bin === 'socat' ? null : `/usr/bin/${bin}`,
)
const result = checkLinuxDependencies()
expect(result.errors).toContain('socat not installed')
expect(result.errors.length).toBe(1)
})
test('returns multiple errors when both bwrap and socat missing', () => {
whichSpy.mockReturnValue(null)
const result = checkLinuxDependencies()
expect(result.errors).toContain('bubblewrap (bwrap) not installed')
expect(result.errors).toContain('socat not installed')
expect(result.errors.length).toBe(2)
})
test('returns warning when apply-seccomp missing', () => {
applySpy.mockReturnValue(null)
const result = checkLinuxDependencies()
expect(result.warnings).toContain(
'seccomp not available - unix socket access not restricted',
)
})
test('passes custom applyPath through to the resolver', () => {
checkLinuxDependencies({ applyPath: '/custom/apply' })
expect(applySpy).toHaveBeenCalledWith('/custom/apply')
})
test('argv0 mode: no seccomp warning even when binary lookup would fail', () => {
applySpy.mockReturnValue(null)
const result = checkLinuxDependencies({
argv0: 'apply-seccomp',
applyPath: '/proc/self/fd/3',
})
expect(result.warnings).toEqual([])
expect(applySpy).not.toHaveBeenCalled()
})
})
describe('getLinuxDependencyStatus', () => {
test('reports all available when everything installed', () => {
const status = getLinuxDependencyStatus()
expect(status.hasBwrap).toBe(true)
expect(status.hasSocat).toBe(true)
expect(status.hasSeccompApply).toBe(true)
})
test('reports bwrap unavailable when not installed', () => {
whichSpy.mockImplementation((bin: string) =>
bin === 'bwrap' ? null : `/usr/bin/${bin}`,
)
const status = getLinuxDependencyStatus()
expect(status.hasBwrap).toBe(false)
expect(status.hasSocat).toBe(true)
})
test('reports socat unavailable when not installed', () => {
whichSpy.mockImplementation((bin: string) =>
bin === 'socat' ? null : `/usr/bin/${bin}`,
)
const status = getLinuxDependencyStatus()
expect(status.hasSocat).toBe(false)
expect(status.hasBwrap).toBe(true)
})
test('reports seccomp unavailable when apply binary missing', () => {
applySpy.mockReturnValue(null)
const status = getLinuxDependencyStatus()
expect(status.hasSeccompApply).toBe(false)
expect(status.hasBwrap).toBe(true)
expect(status.hasSocat).toBe(true)
})
test('argv0 mode: hasSeccompApply is true without touching disk', () => {
applySpy.mockReturnValue(null)
const status = getLinuxDependencyStatus({
argv0: 'apply-seccomp',
applyPath: '/does/not/exist',
})
expect(status.hasSeccompApply).toBe(true)
expect(applySpy).not.toHaveBeenCalled()
})
})