hermes-agent/apps/desktop/electron/windows-sandbox-fallback.test.ts
teknium1 ddd34a98f3 fix(desktop): harden Windows sandbox fallback against false-positive sandbox loss
Follow-up to the salvaged #66803 (@HexLab98):

- Two-strike boot marker: a single mid-boot abort (task-manager kill,
  power loss) no longer disables the sandbox — only a second consecutive
  abort, or a signature-confirmed GPU/renderer STATUS_BREAKPOINT death,
  engages --no-sandbox.
- Version-scoped stickiness: the fallback marker records the app version
  and re-probes the sandbox once after an update (new Electron or
  installer ACL repair may have fixed the host) instead of degrading
  forever. A failed re-probe returns straight to fallback.
- Launch-time icacls repair now runs only when the marker shows a prior
  aborted boot (icacls /T recurses the whole install tree — healthy
  launches skip it; the installer grants the ACE at install time), and
  targets the install dir only. The userData grant is dropped: granting
  S-1-15-2-2 RX on userData would expose Hermes sessions/config to every
  AppContainer app on the machine.
- Renderer crash-loop recovery (same class as #56726, credit @Sahil-SS9
  in PR #57414): a Windows renderer crash loop bearing the breakpoint
  exit code gets the same one-shot --no-sandbox relaunch instead of a
  dead window; unrelated crash loops keep the sandbox.
- Manual --no-sandbox launches are honored but never made sticky.

Tests: 15/15 windows-sandbox-fallback vitest; full desktop electron
suite 432 passed / 1 skipped.
2026-07-18 02:26:19 -07:00

366 lines
11 KiB
TypeScript

import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { test } from 'vitest'
import {
ALL_APPLICATION_PACKAGES_SID,
alreadyHasNoSandbox,
BOOT_ABORTS_BEFORE_FALLBACK,
buildIcaclsGrantArgs,
buildNoSandboxRelaunchArgs,
decideWindowsSandboxLaunch,
fallbackMarker,
grantAllApplicationPackagesAcl,
isWindowsSandboxBreakpointExit,
markerAfterSuccessfulBoot,
parseSandboxMarker,
readSandboxMarker,
sandboxMarkerPath,
shouldAttemptAclRepair,
shouldRelaunchForGpuSandboxCrash,
shouldRelaunchForRendererSandboxCrashLoop,
WINDOWS_SANDBOX_BREAKPOINT_EXIT,
WINDOWS_SANDBOX_MARKER_FILENAME,
writeSandboxMarker
} from './windows-sandbox-fallback'
test('isWindowsSandboxBreakpointExit recognizes signed and unsigned STATUS_BREAKPOINT', () => {
assert.equal(isWindowsSandboxBreakpointExit(WINDOWS_SANDBOX_BREAKPOINT_EXIT), true)
assert.equal(isWindowsSandboxBreakpointExit(-2147483645), true)
assert.equal(isWindowsSandboxBreakpointExit(0x80000003), true)
assert.equal(isWindowsSandboxBreakpointExit(1), false)
assert.equal(isWindowsSandboxBreakpointExit('nope'), false)
})
test('alreadyHasNoSandbox honors argv and ELECTRON_DISABLE_SANDBOX', () => {
assert.equal(alreadyHasNoSandbox(['--foo', '--no-sandbox'], {}), true)
assert.equal(alreadyHasNoSandbox([], { ELECTRON_DISABLE_SANDBOX: '1' }), true)
assert.equal(alreadyHasNoSandbox([], { ELECTRON_DISABLE_SANDBOX: 'true' }), true)
assert.equal(alreadyHasNoSandbox(['--disable-gpu'], {}), false)
})
test('decideWindowsSandboxLaunch stays off outside Windows and on clean markers', () => {
assert.equal(decideWindowsSandboxLaunch({ platform: 'linux', marker: { state: 'booting' } }).enable, false)
const cleanOk = decideWindowsSandboxLaunch({
platform: 'win32',
marker: { state: 'ok' },
argv: [],
env: {}
})
assert.equal(cleanOk.enable, false)
assert.deepEqual(cleanOk.nextMarker, { state: 'booting' })
const noMarker = decideWindowsSandboxLaunch({ platform: 'win32', marker: null, argv: [], env: {} })
assert.equal(noMarker.enable, false)
assert.deepEqual(noMarker.nextMarker, { state: 'booting' })
})
test('a single mid-boot abort does NOT drop the sandbox (two-strike rule)', () => {
// First abort: prior launch left `booting` with no abort count. Could be a
// task-manager kill or power loss — sandbox stays ON, strike recorded.
const first = decideWindowsSandboxLaunch({
platform: 'win32',
marker: { state: 'booting' },
argv: [],
env: {}
})
assert.equal(first.enable, false)
assert.deepEqual(first.nextMarker, { state: 'booting', bootAborts: 1 })
// Second consecutive abort: deterministic crash loop → fallback engages.
const second = decideWindowsSandboxLaunch({
platform: 'win32',
marker: first.nextMarker,
argv: [],
env: {},
appVersion: '1.2.3'
})
assert.equal(second.enable, true)
assert.equal(second.reason, 'boot-loop')
assert.deepEqual(second.nextMarker, { state: 'fallback', reason: 'boot-loop', version: '1.2.3' })
assert.equal(BOOT_ABORTS_BEFORE_FALLBACK, 2)
})
test('sticky fallback persists within one app version', () => {
const decision = decideWindowsSandboxLaunch({
platform: 'win32',
marker: { state: 'fallback', reason: 'gpu-breakpoint', version: '1.2.3' },
argv: [],
env: {},
appVersion: '1.2.3'
})
assert.equal(decision.enable, true)
assert.equal(decision.reason, 'sticky-fallback')
assert.equal(decision.nextMarker.state, 'fallback')
assert.equal(decision.nextMarker.reason, 'gpu-breakpoint')
})
test('an app update re-probes the sandbox once instead of degrading forever', () => {
// Version changed since the fallback engaged → probe with sandbox ON.
const reprobe = decideWindowsSandboxLaunch({
platform: 'win32',
marker: { state: 'fallback', reason: 'boot-loop', version: '1.2.3' },
argv: [],
env: {},
appVersion: '1.3.0'
})
assert.equal(reprobe.enable, false)
assert.equal(reprobe.nextMarker.state, 'booting')
assert.equal(reprobe.nextMarker.reprobe, true)
// The re-probe boot aborted → straight back to fallback, no second strike.
const failedReprobe = decideWindowsSandboxLaunch({
platform: 'win32',
marker: reprobe.nextMarker,
argv: [],
env: {},
appVersion: '1.3.0'
})
assert.equal(failedReprobe.enable, true)
assert.equal(failedReprobe.reason, 'reprobe-failed')
assert.equal(failedReprobe.nextMarker.state, 'fallback')
assert.equal(failedReprobe.nextMarker.version, '1.3.0')
// A legacy fallback marker without a version stays sticky (no re-probe).
const legacy = decideWindowsSandboxLaunch({
platform: 'win32',
marker: { state: 'fallback' },
argv: [],
env: {},
appVersion: '1.3.0'
})
assert.equal(legacy.enable, true)
assert.equal(legacy.reason, 'sticky-fallback')
})
test('manual --no-sandbox is honored but never made sticky', () => {
const manual = decideWindowsSandboxLaunch({
platform: 'win32',
marker: { state: 'ok' },
argv: ['--no-sandbox'],
env: {}
})
assert.equal(manual.enable, true)
assert.equal(manual.reason, 'already-enabled')
assert.equal(manual.nextMarker.state, 'booting')
// But a relaunch-written fallback marker is preserved through the flagged boot.
const relaunched = decideWindowsSandboxLaunch({
platform: 'win32',
marker: { state: 'fallback', reason: 'gpu-breakpoint', version: '1.2.3' },
argv: ['--no-sandbox'],
env: {},
appVersion: '1.2.3'
})
assert.equal(relaunched.enable, true)
assert.equal(relaunched.nextMarker.state, 'fallback')
})
test('marker transitions after a successful boot', () => {
assert.deepEqual(markerAfterSuccessfulBoot({ fallbackActive: false }), { state: 'ok' })
assert.deepEqual(markerAfterSuccessfulBoot({ fallbackActive: true, reason: 'gpu-breakpoint', appVersion: '1.2.3' }), {
state: 'fallback',
reason: 'gpu-breakpoint',
version: '1.2.3'
})
})
test('shouldAttemptAclRepair only fires on evidence of trouble', () => {
assert.equal(shouldAttemptAclRepair(null), false)
assert.equal(shouldAttemptAclRepair({ state: 'ok' }), false)
assert.equal(shouldAttemptAclRepair({ state: 'booting' }), true)
assert.equal(shouldAttemptAclRepair({ state: 'fallback' }), true)
})
test('sandbox marker round-trips through the userData file', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-sandbox-marker-'))
try {
assert.equal(sandboxMarkerPath(dir), path.join(dir, WINDOWS_SANDBOX_MARKER_FILENAME))
assert.equal(readSandboxMarker(dir), null)
writeSandboxMarker(dir, { state: 'booting', bootAborts: 1 })
assert.deepEqual(readSandboxMarker(dir), { state: 'booting', bootAborts: 1 })
writeSandboxMarker(dir, fallbackMarker('renderer-crash-loop', '1.2.3'))
assert.deepEqual(readSandboxMarker(dir), {
state: 'fallback',
reason: 'renderer-crash-loop',
version: '1.2.3'
})
assert.equal(parseSandboxMarker({ state: 'fallback' })?.state, 'fallback')
assert.equal(parseSandboxMarker({ state: 'nope' }), null)
// Unknown reason strings and junk fields are dropped, not fatal.
assert.deepEqual(parseSandboxMarker({ state: 'fallback', reason: 'weird', bootAborts: -3 }), {
state: 'fallback'
})
} finally {
fs.rmSync(dir, { recursive: true, force: true })
}
})
test('buildIcaclsGrantArgs targets ALL APPLICATION PACKAGES with inherited RX', () => {
assert.deepEqual(buildIcaclsGrantArgs('C:\\Hermes\\win-unpacked'), [
'C:\\Hermes\\win-unpacked',
'/grant',
`*${ALL_APPLICATION_PACKAGES_SID}:(OI)(CI)(RX)`,
'/T',
'/C',
'/Q'
])
})
test('grantAllApplicationPackagesAcl is a no-op off Windows and reports exec failures', () => {
assert.deepEqual(grantAllApplicationPackagesAcl('C:\\x', { platform: 'darwin' }), { ok: false })
const calls: Array<{ file: string; args: readonly string[] }> = []
const ok = grantAllApplicationPackagesAcl('C:\\Hermes', {
platform: 'win32',
execFileSync(file, args) {
calls.push({ file, args })
return Buffer.alloc(0)
}
})
assert.deepEqual(ok, { ok: true })
assert.equal(calls.length, 1)
assert.equal(calls[0]?.file, 'icacls')
assert.deepEqual(calls[0]?.args, buildIcaclsGrantArgs('C:\\Hermes'))
const failed = grantAllApplicationPackagesAcl('C:\\Hermes', {
platform: 'win32',
execFileSync() {
throw new Error('access denied')
}
})
assert.equal(failed.ok, false)
assert.match(String(failed.error), /access denied/)
})
test('shouldRelaunchForGpuSandboxCrash only fires once for GPU breakpoint deaths', () => {
assert.equal(
shouldRelaunchForGpuSandboxCrash({
platform: 'win32',
details: { type: 'GPU', exitCode: WINDOWS_SANDBOX_BREAKPOINT_EXIT },
alreadyNoSandbox: false,
relaunchAttempted: false
}),
true
)
assert.equal(
shouldRelaunchForGpuSandboxCrash({
platform: 'win32',
details: { type: 'GPU', exitCode: WINDOWS_SANDBOX_BREAKPOINT_EXIT },
alreadyNoSandbox: true,
relaunchAttempted: false
}),
false
)
assert.equal(
shouldRelaunchForGpuSandboxCrash({
platform: 'win32',
details: { type: 'GPU', exitCode: WINDOWS_SANDBOX_BREAKPOINT_EXIT },
alreadyNoSandbox: false,
relaunchAttempted: true
}),
false
)
assert.equal(
shouldRelaunchForGpuSandboxCrash({
platform: 'win32',
details: { type: 'renderer', exitCode: WINDOWS_SANDBOX_BREAKPOINT_EXIT },
alreadyNoSandbox: false,
relaunchAttempted: false
}),
false
)
assert.equal(
shouldRelaunchForGpuSandboxCrash({
platform: 'linux',
details: { type: 'GPU', exitCode: WINDOWS_SANDBOX_BREAKPOINT_EXIT },
alreadyNoSandbox: false,
relaunchAttempted: false
}),
false
)
})
test('renderer crash-loop relaunch requires the sandbox breakpoint signature', () => {
assert.equal(
shouldRelaunchForRendererSandboxCrashLoop({
platform: 'win32',
reason: 'crashed',
exitCode: WINDOWS_SANDBOX_BREAKPOINT_EXIT,
alreadyNoSandbox: false,
relaunchAttempted: false
}),
true
)
// Unrelated renderer crash loops (plain crash, OOM churn) keep the sandbox.
assert.equal(
shouldRelaunchForRendererSandboxCrashLoop({
platform: 'win32',
reason: 'crashed',
exitCode: 1,
alreadyNoSandbox: false,
relaunchAttempted: false
}),
false
)
assert.equal(
shouldRelaunchForRendererSandboxCrashLoop({
platform: 'win32',
reason: 'oom',
exitCode: WINDOWS_SANDBOX_BREAKPOINT_EXIT,
alreadyNoSandbox: false,
relaunchAttempted: false
}),
false
)
assert.equal(
shouldRelaunchForRendererSandboxCrashLoop({
platform: 'win32',
reason: 'crashed',
exitCode: WINDOWS_SANDBOX_BREAKPOINT_EXIT,
alreadyNoSandbox: true,
relaunchAttempted: false
}),
false
)
assert.equal(
shouldRelaunchForRendererSandboxCrashLoop({
platform: 'linux',
reason: 'crashed',
exitCode: WINDOWS_SANDBOX_BREAKPOINT_EXIT,
alreadyNoSandbox: false,
relaunchAttempted: false
}),
false
)
})
test('buildNoSandboxRelaunchArgs appends a single --no-sandbox flag', () => {
assert.deepEqual(buildNoSandboxRelaunchArgs(['--foo', '--no-sandbox', 'hermes://x']), [
'--foo',
'hermes://x',
'--no-sandbox'
])
})