fix(sandbox): preserve valueless spawn failures (round 2)

This commit is contained in:
Hypatia May
2026-08-04 12:16:21 +08:00
parent e36d040d0a
commit 319376ef1c
3 changed files with 39 additions and 7 deletions

View File

@@ -16,6 +16,7 @@ import type { ConfinedArgv, SandboxExecutionPolicy, SandboxMode, SandboxPolicy }
import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy'
import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import type { SubprocessHandle, SubprocessOutputReader } from '@deepseek-ai/dsh-subprocess'
import { classifyDenial, classifyRunnerFailure } from '../src/helpers.ts'
import type { Config } from '@deepseek-ai/dsh-bash-sandbox'
@@ -372,6 +373,36 @@ describe('background sandbox facts', () => {
expect(accounting.size).toBe(0)
})
it('classifies a spawn rejection whose reason is undefined', async () => {
const { ctx, bash } = await setup()
const emptyReader: SubprocessOutputReader = {
readFrom: () => ({ text: '', nextOffset: 0, lossy: false }),
}
vi.spyOn(ctx.subprocess, 'spawn').mockReturnValue({
pid: -1,
stdin: undefined,
stdout: undefined,
stderr: undefined,
collected: { stdout: emptyReader, stderr: emptyReader },
// Arbitrary subprocess providers can reject without a value; that edge is the point of this test.
// oxlint-disable-next-line typescript/prefer-promise-reject-errors
done: Promise.reject(undefined),
terminate: vi.fn(),
waitForExit: async () => true,
} satisfies SubprocessHandle)
const task = bash.start(bash.resolve({ command: 'true' }))
await task.done
expect(task.readOutput().delta).toContain('spawn failed: undefined')
expect(task.sandbox).toEqual({
mode: 'read-only',
denied: false,
enforcement: 'full',
runnerFailed: true,
})
})
it('stamps a settled denial: nonzero exit + permission stderr under a confined mode', async () => {
const { bash } = await setup()
const task = bash.start(bash.resolve({ command: 'echo "x: Permission denied" >&2; exit 1' }))