test(zcode): verify rollback with real Windows file permissions

This commit is contained in:
Neil
2026-10-10 16:24:14 -07:00
committed by Neil
parent 1f6ab26ec6
commit 6de900fcc6
2 changed files with 175 additions and 0 deletions
@@ -0,0 +1,34 @@
name: Coding Plan credential contract
on:
pull_request:
paths:
- 'src/main/zcode/**'
- 'src/shared/secure-file*'
- 'src/shared/secure-path-windows-acl*'
- 'src/shared/windows-security-descriptor*'
- '.github/workflows/zcode-credential-contract.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: coding-plan-contract-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
credential-files:
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, macos-15, windows-2022]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
env:
ORCA_BACKGROUND_LAUNCH: '1'
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
- name: Verify rollback and native Windows permissions
run: pnpm test src/main/zcode/zcode-plan-api-key-store.test.ts src/main/zcode/zcode-plan-api-key-store-plaintext-rollback.test.ts src/main/zcode/zcode-plan-api-key-store-publication-race.test.ts src/main/zcode/zcode-plan-api-key-store-native-windows.test.ts src/shared/secure-file.test.ts
@@ -0,0 +1,141 @@
import * as fs from 'node:fs'
import * as os from 'node:os'
import { join } from 'node:path'
import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'
import type * as WindowsAcl from '../../shared/secure-path-windows-acl'
import type * as Store from './zcode-plan-api-key-store'
type NativeCase = {
home: string
fault: 'none' | 'before-restore' | 'after-restore' | 'competing'
reject: boolean
writes: number
renames: number
stages: number
nativeOutcomes: boolean[]
directories: boolean[]
}
const state = vi.hoisted((): NativeCase => ({
home: '',
fault: 'none',
reject: false,
writes: 0,
renames: 0,
stages: 0,
nativeOutcomes: [],
directories: []
}))
vi.mock('electron', () => ({ safeStorage: { isEncryptionAvailable: () => false } }))
vi.mock('node:os', async (original) => ({
...(await original<typeof os>()),
homedir: () => state.home
}))
vi.mock('node:fs', async (original) => {
const real = await original<typeof fs>()
return {
...real,
writeFileSync: (...args: Parameters<typeof fs.writeFileSync>) => {
if (++state.writes === 2 && state.fault === 'before-restore') {
throw new Error('Synthetic restore staging failure')
}
return real.writeFileSync(...args)
},
renameSync: (...args: Parameters<typeof fs.renameSync>) => {
real.renameSync(...args)
if (++state.renames === 2 && state.fault === 'after-restore') {
throw new Error('Synthetic failure after restore publication')
}
}
}
})
vi.mock('../../shared/secure-path-windows-acl', async (original) => {
const real = await original<typeof WindowsAcl>()
const files = await vi.importActual<typeof fs>('node:fs')
return {
...real,
restrictWindowsPathSync: (...args: Parameters<typeof WindowsAcl.restrictWindowsPathSync>) => {
const outcome = real.restrictWindowsPathSync(...args)
state.nativeOutcomes.push(outcome)
if (state.fault === 'competing' && args[0].endsWith('.tmp') && ++state.stages === 2) {
const competing = join(state.home, '.orca', 'competing-key')
files.writeFileSync(competing, envelope('synthetic-competing'))
expect(real.restrictWindowsPathSync(competing, false)).toBe(true)
files.renameSync(competing, join(state.home, '.orca', 'zcode-plan-api-key.enc'))
}
return state.reject ? false : outcome
},
bestEffortRestrictWindowsPath: (
path: string,
directory: boolean,
settled?: (restricted: boolean) => void
) =>
real.bestEffortRestrictWindowsPath(path, directory, (restricted) => {
state.directories.push(restricted)
settled?.(restricted)
})
}
})
const envelope = (key: string) =>
`orca-zcode-plan-api-key:v1:plaintext:${Buffer.from(key).toString('base64')}`
let store: typeof Store
let keyPath: string
describe.skipIf(process.platform !== 'win32')('native Windows Coding Plan rollback', () => {
beforeEach(async () => {
state.home = fs.mkdtempSync(join(os.tmpdir(), 'orca-native-plan-'))
state.fault = 'none'
state.reject = false
state.writes = 0
state.renames = 0
state.stages = 0
state.nativeOutcomes = []
state.directories = []
keyPath = join(state.home, '.orca', 'zcode-plan-api-key.enc')
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
vi.resetModules()
store = await import('./zcode-plan-api-key-store')
store.saveZcodePlanApiKey('synthetic-previous')
expect(fs.readFileSync(keyPath, 'utf8')).toBe(envelope('synthetic-previous'))
expect(state.nativeOutcomes.length).toBeGreaterThan(0)
expect(state.nativeOutcomes.every(Boolean)).toBe(true)
await vi.waitFor(
() => {
expect(state.directories.length).toBeGreaterThan(0)
expect(state.directories.every(Boolean)).toBe(true)
},
{ timeout: 15_000 }
)
state.writes = 0
state.renames = 0
state.stages = 0
state.reject = true
})
afterEach(() => {
vi.restoreAllMocks()
fs.rmSync(state.home, { recursive: true, force: true })
})
it.each(['before-restore', 'after-restore', 'competing'] as const)(
'preserves the file contract after %s with real file and directory ACLs',
(fault) => {
state.fault = fault
expect(() => store.saveZcodePlanApiKey('synthetic-rejected')).toThrow(
'could not be stored securely'
)
if (fault === 'before-restore') {
expect(fs.existsSync(keyPath)).toBe(false)
} else {
expect(fs.readFileSync(keyPath, 'utf8')).toBe(
envelope(fault === 'competing' ? 'synthetic-competing' : 'synthetic-previous')
)
}
expect(fs.readdirSync(join(state.home, '.orca')).some((name) => name.endsWith('.tmp'))).toBe(
false
)
expect(state.nativeOutcomes.every(Boolean)).toBe(true)
},
30_000
)
})