From 27bedb2879cff0a8edde69dfb1ee13a96184b5a3 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Mon, 1 Jun 2026 18:10:09 +0200 Subject: [PATCH] docs: disclose codex isolation limits --- .../content/docs/cli-reference/ktx-status.mdx | 6 ++++++ .../content/docs/guides/llm-configuration.mdx | 11 +++++++++-- .../cli/src/context/llm/codex-isolation.ts | 9 +++++++++ packages/cli/src/setup-models.ts | 2 ++ packages/cli/src/status-project.ts | 11 +++++++++++ .../test/context/llm/codex-isolation.test.ts | 19 +++++++++++++++++++ packages/cli/test/setup-models.test.ts | 2 ++ packages/cli/test/status-project.test.ts | 10 ++++++++++ 8 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 packages/cli/src/context/llm/codex-isolation.ts create mode 100644 packages/cli/test/context/llm/codex-isolation.test.ts diff --git a/docs-site/content/docs/cli-reference/ktx-status.mdx b/docs-site/content/docs/cli-reference/ktx-status.mdx index 59047ee4..66e4964c 100644 --- a/docs-site/content/docs/cli-reference/ktx-status.mdx +++ b/docs-site/content/docs/cli-reference/ktx-status.mdx @@ -61,6 +61,12 @@ For `llm.provider.backend: codex`, `ktx status` runs a minimal non-interactive Codex request. If the probe fails, authenticate Codex locally with the Codex CLI and verify the Codex CLI installation. +When `llm.provider.backend: codex` is configured, `ktx status` also prints a +warning when the installed public Codex SDK and CLI surface cannot prove full +Claude-Code-style isolation. The warning does not block authenticated Codex +usage, but it marks the project status as partial so you can make an explicit +runtime-isolation decision. + A `Local data` section summarises what the project has accumulated locally: ingest run counts, last completed timestamp per connection, knowledge page counts by scope, semantic-layer source and dictionary value counts, and the diff --git a/docs-site/content/docs/guides/llm-configuration.mdx b/docs-site/content/docs/guides/llm-configuration.mdx index c9e7abeb..93895ca3 100644 --- a/docs-site/content/docs/guides/llm-configuration.mdx +++ b/docs-site/content/docs/guides/llm-configuration.mdx @@ -74,8 +74,15 @@ enrichment, memory, and other LLM-backed work through Codex. During runtime loops, **ktx** starts a temporary loopback MCP server for the current run, exposes only the tools passed to that run, asks Codex to use a -read-only sandbox with approval disabled, auto-approves only those run-scoped -MCP tools, and disables Codex web search. +read-only sandbox, sets `approval_policy=never`, auto-approves only those +run-scoped MCP tools, and disables Codex web search. + +Codex backend isolation is currently limited by the public Codex SDK and CLI +surface. Codex may still load user Codex config and built-in command execution +or read-only file capabilities. Use `llm.provider.backend: claude-code` when +you need stricter Claude-Code-style runtime tool isolation, or remove host +Codex MCP and tool config before running untrusted prompts through the `codex` +backend. ## Prompt caching diff --git a/packages/cli/src/context/llm/codex-isolation.ts b/packages/cli/src/context/llm/codex-isolation.ts new file mode 100644 index 00000000..d54ac1f8 --- /dev/null +++ b/packages/cli/src/context/llm/codex-isolation.ts @@ -0,0 +1,9 @@ +export const CODEX_ISOLATION_WARNING = + 'Codex backend isolation is limited by the public Codex SDK/CLI surface: ktx restricts the runtime MCP server to the current ktx tool set, disables Codex web search, asks for a read-only sandbox, and sets approval_policy=never, but Codex may still load user Codex config and built-in command execution or read-only file capabilities.'; + +export const CODEX_ISOLATION_WARNING_FIX = + 'Use llm.provider.backend: claude-code when you need stricter Claude-Code-style runtime tool isolation, or remove host Codex MCP/tool config before running untrusted prompts through the codex backend.'; + +export function formatCodexIsolationWarning(): string { + return `${CODEX_ISOLATION_WARNING} ${CODEX_ISOLATION_WARNING_FIX}`; +} diff --git a/packages/cli/src/setup-models.ts b/packages/cli/src/setup-models.ts index 0d2fc439..c224e8db 100644 --- a/packages/cli/src/setup-models.ts +++ b/packages/cli/src/setup-models.ts @@ -2,6 +2,7 @@ import { execFile } from 'node:child_process'; import { writeFile } from 'node:fs/promises'; import { promisify } from 'node:util'; import { runClaudeCodeAuthProbe } from './context/llm/claude-code-runtime.js'; +import { formatCodexIsolationWarning } from './context/llm/codex-isolation.js'; import { runCodexAuthProbe } from './context/llm/codex-runtime.js'; import { resolveLocalKtxLlmConfig } from './context/llm/local-config.js'; import { resolveKtxConfigReference } from './context/core/config-reference.js'; @@ -1113,6 +1114,7 @@ export async function runKtxSetupAnthropicModelStep( io.stderr.write(`${health.message}\n`); return { status: 'failed', projectDir: args.projectDir }; } + io.stderr.write(`${formatCodexIsolationWarning()}\n`); await persistLlmConfig(args.projectDir, { backend: 'codex' }, model.model); io.stdout.write(`│ LLM ready: yes (codex, ${model.model})\n`); return { status: 'ready', projectDir: args.projectDir }; diff --git a/packages/cli/src/status-project.ts b/packages/cli/src/status-project.ts index 3bf3b0f6..2f37a635 100644 --- a/packages/cli/src/status-project.ts +++ b/packages/cli/src/status-project.ts @@ -1,6 +1,10 @@ import { stat as statAsync, readdir as readdirAsync } from 'node:fs/promises'; import { basename, join } from 'node:path'; import { runClaudeCodeAuthProbe } from './context/llm/claude-code-runtime.js'; +import { + CODEX_ISOLATION_WARNING, + CODEX_ISOLATION_WARNING_FIX, +} from './context/llm/codex-isolation.js'; import { runCodexAuthProbe } from './context/llm/codex-runtime.js'; import type { KtxConfigIssue, KtxProjectConfig, KtxProjectConnectionConfig, KtxProjectEmbeddingConfig, KtxProjectLlmConfig } from './context/project/config.js'; import type { KtxLocalProject } from './context/project/project.js'; @@ -609,6 +613,13 @@ function buildWarnings( }); } + if (llm.backend === 'codex') { + warnings.push({ + message: CODEX_ISOLATION_WARNING, + fix: CODEX_ISOLATION_WARNING_FIX, + }); + } + return warnings; } diff --git a/packages/cli/test/context/llm/codex-isolation.test.ts b/packages/cli/test/context/llm/codex-isolation.test.ts new file mode 100644 index 00000000..0ef39ee3 --- /dev/null +++ b/packages/cli/test/context/llm/codex-isolation.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from 'vitest'; +import { + CODEX_ISOLATION_WARNING, + CODEX_ISOLATION_WARNING_FIX, + formatCodexIsolationWarning, +} from '../../../src/context/llm/codex-isolation.js'; + +describe('Codex isolation warning', () => { + it('documents the enforced and unenforced Codex isolation boundaries', () => { + expect(CODEX_ISOLATION_WARNING).toContain('runtime MCP server to the current ktx tool set'); + expect(CODEX_ISOLATION_WARNING).toContain('disables Codex web search'); + expect(CODEX_ISOLATION_WARNING).toContain('may still load user Codex config'); + expect(CODEX_ISOLATION_WARNING).toContain('built-in command execution'); + expect(CODEX_ISOLATION_WARNING_FIX).toContain('claude-code'); + expect(formatCodexIsolationWarning()).toBe( + `${CODEX_ISOLATION_WARNING} ${CODEX_ISOLATION_WARNING_FIX}`, + ); + }); +}); diff --git a/packages/cli/test/setup-models.test.ts b/packages/cli/test/setup-models.test.ts index 9aacfddc..b58c5a31 100644 --- a/packages/cli/test/setup-models.test.ts +++ b/packages/cli/test/setup-models.test.ts @@ -240,6 +240,8 @@ describe('setup Anthropic model step', () => { models: { default: 'gpt-5.3-codex' }, }); expect(codexAuthProbe).toHaveBeenCalledWith(expect.objectContaining({ projectDir: tempDir, model: 'gpt-5.3-codex' })); + expect(io.stderr()).toContain('Codex backend isolation is limited'); + expect(io.stderr()).toContain('may still load user Codex config'); }); it('prompts for the Claude Code model during interactive setup', async () => { diff --git a/packages/cli/test/status-project.test.ts b/packages/cli/test/status-project.test.ts index 1a6f01a3..df604cfd 100644 --- a/packages/cli/test/status-project.test.ts +++ b/packages/cli/test/status-project.test.ts @@ -415,6 +415,16 @@ describe('buildProjectStatus codex', () => { status: 'ok', detail: 'local Codex session authenticated', }); + expect(status.warnings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: expect.stringContaining('Codex backend isolation is limited'), + fix: expect.stringContaining('claude-code'), + }), + ]), + ); + const rendered = renderProjectStatus(status, { verbose: false, useColor: false }); + expect(rendered).toContain('Codex backend isolation is limited'); }); it('skips Codex auth probe with --fast', async () => {