fix(sandbox): round 6 bind spawn provenance
This commit is contained in:
@@ -23,10 +23,11 @@ function isUsableWorkdir(path: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute only executable-class failures with Node spawn provenance after
|
* Attribute only executable-class failures with positive Node argv[0]
|
||||||
* independently ruling out the caller-owned cwd. With a usable cwd, these
|
* provenance after independently ruling out the caller-owned cwd. A supplied
|
||||||
* codes describe resolution, permissions, or loading of the provider's
|
* error path must exactly identify the runner; without one, the syscall must.
|
||||||
* argv[0], including a script whose shebang interpreter is unavailable.
|
* With a usable cwd, these codes describe resolution, permissions, or loading
|
||||||
|
* of that argv[0], including a script whose shebang interpreter is unavailable.
|
||||||
* @param error - the original spawn rejection.
|
* @param error - the original spawn rejection.
|
||||||
* @param runnerProgram - provider argv[0], the executable that establishes confinement.
|
* @param runnerProgram - provider argv[0], the executable that establishes confinement.
|
||||||
* @param workdir - the caller-owned spawn cwd, checked independently for usability.
|
* @param workdir - the caller-owned spawn cwd, checked independently for usability.
|
||||||
@@ -41,9 +42,11 @@ export function isRunnerSpawnFailure(
|
|||||||
if (typeof error !== 'object' || error === null) return false
|
if (typeof error !== 'object' || error === null) return false
|
||||||
const { code, path, syscall } = error as { code?: unknown; path?: unknown; syscall?: unknown }
|
const { code, path, syscall } = error as { code?: unknown; path?: unknown; syscall?: unknown }
|
||||||
if (typeof code !== 'string' || !EXECUTABLE_SPAWN_CODES.has(code)) return false
|
if (typeof code !== 'string' || !EXECUTABLE_SPAWN_CODES.has(code)) return false
|
||||||
if (typeof syscall !== 'string' || (syscall !== 'spawn' && !syscall.startsWith('spawn '))) return false
|
if (typeof syscall !== 'string') return false
|
||||||
if (path !== undefined && (typeof path !== 'string' || (path.length > 0 && path !== runnerProgram))) return false
|
const exactSyscall = `spawn ${runnerProgram}`
|
||||||
return true
|
if (path === undefined) return syscall === exactSyscall
|
||||||
|
if (typeof path !== 'string' || path.length === 0 || path !== runnerProgram) return false
|
||||||
|
return syscall === 'spawn' || syscall === exactSyscall
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fatal runner evidence retained for infrastructure-error detail. */
|
/** Fatal runner evidence retained for infrastructure-error detail. */
|
||||||
|
|||||||
@@ -384,6 +384,8 @@ describe('isRunnerSpawnFailure', () => {
|
|||||||
const missingRunner = join(spillDir, 'definitely-missing-runner')
|
const missingRunner = join(spillDir, 'definitely-missing-runner')
|
||||||
const spawnError = (code: unknown, syscall: unknown = `spawn ${missingRunner}`, path: unknown = missingRunner) =>
|
const spawnError = (code: unknown, syscall: unknown = `spawn ${missingRunner}`, path: unknown = missingRunner) =>
|
||||||
Object.assign(new Error('spawn failed'), { code, syscall, path })
|
Object.assign(new Error('spawn failed'), { code, syscall, path })
|
||||||
|
const spawnErrorWithoutPath = (syscall: string) =>
|
||||||
|
Object.assign(new Error('spawn failed'), { code: 'ENOENT', syscall })
|
||||||
|
|
||||||
expect(isRunnerSpawnFailure(spawnError('EMFILE'), missingRunner, process.cwd())).toBe(false)
|
expect(isRunnerSpawnFailure(spawnError('EMFILE'), missingRunner, process.cwd())).toBe(false)
|
||||||
expect(isRunnerSpawnFailure(spawnError('ENOMEM'), missingRunner, process.cwd())).toBe(false)
|
expect(isRunnerSpawnFailure(spawnError('ENOMEM'), missingRunner, process.cwd())).toBe(false)
|
||||||
@@ -392,12 +394,24 @@ describe('isRunnerSpawnFailure', () => {
|
|||||||
expect(isRunnerSpawnFailure(spawnError('ENOENT', 1), missingRunner, process.cwd())).toBe(false)
|
expect(isRunnerSpawnFailure(spawnError('ENOENT', 1), missingRunner, process.cwd())).toBe(false)
|
||||||
expect(isRunnerSpawnFailure(spawnError('ENOENT', 'spawn', process.execPath), missingRunner, process.cwd())).toBe(false)
|
expect(isRunnerSpawnFailure(spawnError('ENOENT', 'spawn', process.execPath), missingRunner, process.cwd())).toBe(false)
|
||||||
expect(isRunnerSpawnFailure(spawnError('ENOENT', 'spawn', 1), missingRunner, process.cwd())).toBe(false)
|
expect(isRunnerSpawnFailure(spawnError('ENOENT', 'spawn', 1), missingRunner, process.cwd())).toBe(false)
|
||||||
expect(isRunnerSpawnFailure(spawnError('ENOENT', 'spawn', ''), missingRunner, process.cwd())).toBe(true)
|
expect(isRunnerSpawnFailure(spawnError('ENOENT', 'spawn', ''), missingRunner, process.cwd())).toBe(false)
|
||||||
expect(isRunnerSpawnFailure(spawnError('ENOENT', 'spawn', undefined), missingRunner, process.cwd())).toBe(true)
|
expect(isRunnerSpawnFailure(spawnErrorWithoutPath('spawn'), missingRunner, process.cwd())).toBe(false)
|
||||||
|
expect(isRunnerSpawnFailure(spawnErrorWithoutPath('spawn other-runner'), missingRunner, process.cwd())).toBe(false)
|
||||||
expect(isRunnerSpawnFailure(undefined, missingRunner, process.cwd())).toBe(false)
|
expect(isRunnerSpawnFailure(undefined, missingRunner, process.cwd())).toBe(false)
|
||||||
expect(isRunnerSpawnFailure(null, missingRunner, process.cwd())).toBe(false)
|
expect(isRunnerSpawnFailure(null, missingRunner, process.cwd())).toBe(false)
|
||||||
expect(isRunnerSpawnFailure(spawnError('ENOENT'), undefined, process.cwd())).toBe(false)
|
expect(isRunnerSpawnFailure(spawnError('ENOENT'), undefined, process.cwd())).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('accepts only syscall provenance compatible with the exact runner program', () => {
|
||||||
|
const runner = join(spillDir, 'runner with spaces')
|
||||||
|
const spawnError = (syscall: string, path?: string) =>
|
||||||
|
Object.assign(new Error('spawn failed'), { code: 'ENOEXEC', syscall, path })
|
||||||
|
|
||||||
|
expect(isRunnerSpawnFailure(spawnError('spawn', runner), runner, process.cwd())).toBe(true)
|
||||||
|
expect(isRunnerSpawnFailure(spawnError(`spawn ${runner}`, runner), runner, process.cwd())).toBe(true)
|
||||||
|
expect(isRunnerSpawnFailure(spawnError(`spawn ${runner}`), runner, process.cwd())).toBe(true)
|
||||||
|
expect(isRunnerSpawnFailure(spawnError('spawn other-runner', runner), runner, process.cwd())).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('classifyRunnerFailure', () => {
|
describe('classifyRunnerFailure', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user