fix(codex): make Windows trust upgrade transactional

This commit is contained in:
Brennan Benson
2026-07-17 17:41:14 -07:00
parent 9caba0a216
commit e25985de73
4 changed files with 46 additions and 4 deletions
@@ -4,6 +4,7 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import type { CodexManagedAccount, GlobalSettings } from '../../shared/types'
import type * as NodeOs from 'node:os'
import { readHookTrustEntries } from '../codex/config-toml-trust'
const testState = { userData: '', home: '' }
const previousEnv: Record<string, string | undefined> = {}
@@ -98,9 +99,9 @@ describe('CodexRuntimeHomeService per-account takeover composition', () => {
expect(readFileSync(join(account.managedHomePath, 'hooks.json'), 'utf8')).toContain(
process.platform === 'win32' ? 'codex-hook.cmd' : 'codex-hook.sh'
)
expect(readFileSync(join(account.managedHomePath, 'config.toml'), 'utf8')).toContain(
'[hooks.state."'
)
expect(
readHookTrustEntries(join(account.managedHomePath, 'config.toml')).size
).toBeGreaterThan(0)
}
const discoveryHomes = service.getHostCodexHomePathsForSessionDiscovery()
@@ -19,7 +19,9 @@ import {
import { readCodexTrustGrantLedgerHome } from './codex-trust-grant-ledger'
import {
computeTrustKey,
computeTrustedHash,
normalizeHookTrustKeyForLookup,
readHookTrustEntries,
upsertHookTrustEntries,
type CodexTrustEntry
} from './config-toml-trust'
@@ -133,6 +135,26 @@ describe('grantManagedCodexHookTrust', () => {
expect(invocation.envToDelete).toContain('CODEX_HOME')
})
it('removes equivalent Windows fallback keys before the RPC writes canonical trust', () => {
const entry: CodexTrustEntry = {
...managedEntry('stop'),
sourcePath: String.raw`C:\Users\Alice\.codex\hooks.json`
}
const plan = buildPlan([entry])
upsertHookTrustEntries(plan.tomlPath, [entry])
expect(readHookTrustEntries(plan.tomlPath).get(computeTrustKey(entry))?.trustedHash).toBe(
computeTrustedHash(entry)
)
const runner = vi.fn(() => {
expect(readHookTrustEntries(plan.tomlPath).has(computeTrustKey(entry))).toBe(false)
return grantedSessionResult([entry])
})
_internals.setGrantSessionRunnerSync(runner)
expect(grantManagedCodexHookTrust(plan)).toMatchObject({ lane: 'rpc' })
expect(runner).toHaveBeenCalledTimes(1)
})
it('skips the RPC session while the ledger grant still holds, and re-grants on config drift', () => {
const entries = [managedEntry('session_start')]
const runner = vi.fn(() => grantedSessionResult(entries))
+18
View File
@@ -15,8 +15,10 @@ import {
} from './codex-trust-grant-ledger'
import {
computeTrustKey,
computeTrustedHash,
normalizeHookTrustKeyForLookup,
readHookTrustEntries,
removeHookTrustEntries,
type CodexTrustEntry
} from './config-toml-trust'
import { getCodexHookTrustSignature } from './codex-hook-identity'
@@ -149,6 +151,19 @@ function buildExpectedEntries(plan: CodexManagedTrustGrantPlan): ExpectedManaged
}))
}
function removeSelfComputedTrustBeforeGrant(plan: CodexManagedTrustGrantPlan): void {
const trustStates = readHookTrustEntries(plan.tomlPath)
const ownedKeys = plan.managedEntries
.map((entry) => {
const key = computeTrustKey(entry)
return trustStates.get(key)?.trustedHash === computeTrustedHash(entry) ? key : null
})
.filter((key): key is string => key !== null)
if (ownedKeys.length > 0) {
removeHookTrustEntries(plan.tomlPath, ownedKeys)
}
}
function findLedgerGrant(
plan: CodexManagedTrustGrantPlan,
expected: ExpectedManagedEntry[],
@@ -224,6 +239,9 @@ export function grantManagedCodexHookTrust(
const configSnapshot = captureCodexTrustConfig(plan.tomlPath)
let result: CodexHookTrustGrantSessionResult
try {
// Why: Windows fallback writes equivalent separator variants that Codex's
// canonical RPC key may not overwrite, leaving conflicting logical trust.
removeSelfComputedTrustBeforeGrant(plan)
result = runSessionSync(
resolvedHost.buildRequest({
runtimeHomePath: plan.runtimeHomePath,
@@ -19,6 +19,7 @@ import {
computeTrustKey,
computeTrustedHash,
escapeTomlString,
normalizeHookTrustKeyForLookup,
parseTrustKey,
readHookTrustEntries,
upsertHookTrustEntries,
@@ -214,7 +215,7 @@ describe('CodexHookService app-server trust grant lane', () => {
writeCodexTrustGrantLedgerHome(systemHome, {
binary: null,
entries: {
[trustKey]: {
[normalizeHookTrustKeyForLookup(trustKey)]: {
signature: getCodexHookTrustSignature(entry),
trustedHash
}