Guard Codex launches with managed network requirements

This commit is contained in:
Neil
2026-06-27 04:06:25 -07:00
parent 38483d5a69
commit b6c3661e1a
4 changed files with 234 additions and 2 deletions
@@ -0,0 +1,83 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
const { execFileSyncMock, existsSyncMock, readFileSyncMock } = vi.hoisted(() => ({
execFileSyncMock: vi.fn(),
existsSyncMock: vi.fn(),
readFileSyncMock: vi.fn()
}))
vi.mock('node:child_process', () => ({
execFileSync: execFileSyncMock
}))
vi.mock('node:fs', () => ({
existsSync: existsSyncMock,
readFileSync: readFileSyncMock
}))
import {
buildCodexManagedNetworkRequirementsWarningCommand,
hasManagedCodexNetworkPermissionRequirement
} from './codex-managed-network-requirements'
const REPRO_REQUIREMENTS = [
'default_permissions = "github_only"',
'',
'[permissions.github_only]',
'extends = ":read-only"',
'',
'[permissions.github_only.network]',
'enabled = true',
'allow_local_binding = false',
''
].join('\n')
describe('codex managed network requirements', () => {
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
afterEach(() => {
if (originalPlatform) {
Object.defineProperty(process, 'platform', originalPlatform)
}
vi.clearAllMocks()
})
it('detects macOS MDM requirements with enabled permission-profile networking', () => {
Object.defineProperty(process, 'platform', { configurable: true, value: 'darwin' })
execFileSyncMock.mockReturnValue(`${Buffer.from(REPRO_REQUIREMENTS).toString('base64')}\n`)
existsSyncMock.mockReturnValue(false)
expect(hasManagedCodexNetworkPermissionRequirement()).toBe(true)
expect(execFileSyncMock).toHaveBeenCalledWith(
'defaults',
['read', 'com.openai.codex', 'requirements_toml_base64'],
expect.objectContaining({ encoding: 'utf-8' })
)
})
it('detects system requirements with enabled permission-profile networking', () => {
Object.defineProperty(process, 'platform', { configurable: true, value: 'linux' })
existsSyncMock.mockReturnValue(true)
readFileSyncMock.mockReturnValue(REPRO_REQUIREMENTS)
expect(hasManagedCodexNetworkPermissionRequirement()).toBe(true)
})
it('ignores requirements without permission-profile network enablement', () => {
Object.defineProperty(process, 'platform', { configurable: true, value: 'darwin' })
execFileSyncMock.mockReturnValue(
`${Buffer.from('[permissions.github_only.network]\\nenabled = false\\n').toString('base64')}\n`
)
existsSyncMock.mockReturnValue(false)
expect(hasManagedCodexNetworkPermissionRequirement()).toBe(false)
})
it('builds a shell-safe warning command', () => {
const command = buildCodexManagedNetworkRequirementsWarningCommand()
expect(command).toContain('printf')
expect(command).toContain('managed Codex requirements')
expect(command).toContain('turn/start failed in TUI')
})
})
@@ -0,0 +1,95 @@
import { execFileSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
const MACOS_CODEX_PREFERENCES_DOMAIN = 'com.openai.codex'
const MACOS_REQUIREMENTS_KEY = 'requirements_toml_base64'
const UNIX_REQUIREMENTS_PATH = '/etc/codex/requirements.toml'
const WINDOWS_REQUIREMENTS_PATH = join(
process.env.ProgramData ?? 'C:\\ProgramData',
'OpenAI',
'Codex',
'requirements.toml'
)
const KNOWN_BAD_WARNING_LINES = [
'Orca did not start Codex because this Mac has managed Codex requirements with network.enabled = true in a permission profile.',
'Codex CLI currently exits with "turn/start failed in TUI" for that managed network policy.',
'Ask your Codex administrator to remove that network.enabled requirement or update Codex after the upstream fix lands.'
] as const
export function buildCodexManagedNetworkRequirementsWarningCommand(): string {
return `printf '%s\\n' ${KNOWN_BAD_WARNING_LINES.map(shellQuote).join(' ')}`
}
export function hasManagedCodexNetworkPermissionRequirement(): boolean {
return getManagedRequirementsTomlCandidates().some(hasNetworkEnabledPermissionProfile)
}
function getManagedRequirementsTomlCandidates(): string[] {
const candidates: string[] = []
const mdmRequirements = readMacosMdmRequirementsToml()
if (mdmRequirements) {
candidates.push(mdmRequirements)
}
const filePath = process.platform === 'win32' ? WINDOWS_REQUIREMENTS_PATH : UNIX_REQUIREMENTS_PATH
const fileRequirements = readRequirementsFile(filePath)
if (fileRequirements) {
candidates.push(fileRequirements)
}
return candidates
}
function readMacosMdmRequirementsToml(): string | null {
if (process.platform !== 'darwin') {
return null
}
try {
const encoded = execFileSync(
'defaults',
['read', MACOS_CODEX_PREFERENCES_DOMAIN, MACOS_REQUIREMENTS_KEY],
{
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 1000
}
).trim()
if (!encoded) {
return null
}
return Buffer.from(encoded, 'base64').toString('utf-8')
} catch {
return null
}
}
function readRequirementsFile(path: string): string | null {
try {
return existsSync(path) ? readFileSync(path, 'utf-8') : null
} catch {
return null
}
}
function hasNetworkEnabledPermissionProfile(toml: string): boolean {
let inPermissionNetworkSection = false
for (const rawLine of toml.split(/\r?\n/)) {
const line = rawLine.trim()
if (line.startsWith('[')) {
inPermissionNetworkSection = /^\[permissions\..+\.network\]\s*(?:#.*)?$/.test(line)
continue
}
if (!inPermissionNetworkSection) {
continue
}
if (/^enabled\s*=\s*true\s*(?:#.*)?$/i.test(line)) {
return true
}
}
return false
}
function shellQuote(value: string): string {
return `'${value.replace(/'/g, "'\\''")}'`
}
+42 -2
View File
@@ -8,7 +8,9 @@ const {
mkdirSyncMock,
writeFileSyncMock,
spawnMock,
resolveAgentForegroundProcessMock
resolveAgentForegroundProcessMock,
hasManagedCodexNetworkPermissionRequirementMock,
buildCodexManagedNetworkRequirementsWarningCommandMock
} = vi.hoisted(() => ({
existsSyncMock: vi.fn(),
statSyncMock: vi.fn(),
@@ -16,7 +18,9 @@ const {
mkdirSyncMock: vi.fn(),
writeFileSyncMock: vi.fn(),
spawnMock: vi.fn(),
resolveAgentForegroundProcessMock: vi.fn()
resolveAgentForegroundProcessMock: vi.fn(),
hasManagedCodexNetworkPermissionRequirementMock: vi.fn(),
buildCodexManagedNetworkRequirementsWarningCommandMock: vi.fn()
}))
vi.mock('fs', () => ({
@@ -43,6 +47,12 @@ vi.mock('./agent-foreground-process', () => ({
resolveAgentForegroundProcess: resolveAgentForegroundProcessMock
}))
vi.mock('../codex/codex-managed-network-requirements', () => ({
hasManagedCodexNetworkPermissionRequirement: hasManagedCodexNetworkPermissionRequirementMock,
buildCodexManagedNetworkRequirementsWarningCommand:
buildCodexManagedNetworkRequirementsWarningCommandMock
}))
vi.mock('../wsl', () => ({
parseWslPath: (path: string) => {
const match = path.match(/^\\\\wsl\.localhost\\([^\\]+)(.*)$/)
@@ -96,6 +106,10 @@ describe('LocalPtyProvider', () => {
resolveAgentForegroundProcessMock.mockImplementation(
async (_pid: number, fallbackProcess: string | null) => fallbackProcess
)
hasManagedCodexNetworkPermissionRequirementMock.mockReturnValue(false)
buildCodexManagedNetworkRequirementsWarningCommandMock.mockReturnValue(
"printf '%s\\n' 'Codex managed network requirements are not supported'"
)
exitCb = undefined
mockProc = {
@@ -268,6 +282,32 @@ describe('LocalPtyProvider', () => {
}
})
it('prints a diagnostic instead of launching Codex when managed network requirements would crash the TUI', async () => {
vi.useFakeTimers()
try {
Object.defineProperty(process, 'platform', { configurable: true, value: 'darwin' })
hasManagedCodexNetworkPermissionRequirementMock.mockReturnValue(true)
await provider.spawn({
cols: 80,
rows: 24,
command: "codex '--dangerously-bypass-approvals-and-sandbox'"
})
const dataCallback = mockProc.onData.mock.calls[0]?.[0] as (data: string) => void
dataCallback('\x1b]777;orca-shell-ready\x07user@host % ')
await Promise.resolve()
vi.advanceTimersByTime(30)
await Promise.resolve()
expect(mockProc.write).toHaveBeenCalledWith(
"printf '%s\\n' 'Codex managed network requirements are not supported'\n"
)
} finally {
vi.useRealTimers()
}
})
it('releases held marker-prefix bytes when local shell readiness times out', async () => {
vi.useFakeTimers()
const onData = vi.fn()
+14
View File
@@ -49,6 +49,10 @@ import { resolveAgentForegroundProcess } from './agent-foreground-process'
import { getAgentForegroundContextPaths } from './agent-foreground-context-paths'
import { recognizeAgentProcessFromCommandLine } from '../../shared/agent-process-recognition'
import { shouldUseShellReadyStartupDelivery } from '../../shared/codex-startup-delivery'
import {
buildCodexManagedNetworkRequirementsWarningCommand,
hasManagedCodexNetworkPermissionRequirement
} from '../codex/codex-managed-network-requirements'
const PANE_IDENTITY_ENV_KEYS = [
'ORCA_PANE_KEY',
@@ -476,6 +480,16 @@ export class LocalPtyProvider implements IPtyProvider {
const isWslShell = Boolean(wslInfo) || pathWin32.basename(shellPath).toLowerCase() === 'wsl.exe'
const launchWslDistro =
wslInfo?.distro ?? worktreeWslContext?.distro ?? preferredWslContext?.distro ?? null
if (
process.platform === 'darwin' &&
args.command &&
recognizeAgentProcessFromCommandLine(args.command)?.agent === 'codex' &&
hasManagedCodexNetworkPermissionRequirement()
) {
// Why: Codex 0.142 exits the TUI before the first turn when managed
// requirements enable a permission-profile network policy on macOS.
args.command = buildCodexManagedNetworkRequirementsWarningCommand()
}
const finalEnv = this.opts.buildSpawnEnv
? this.opts.buildSpawnEnv(id, spawnEnv, {
command: args.command,