docs: disclose codex isolation limits

This commit is contained in:
Andrey Avtomonov 2026-06-01 18:10:09 +02:00
parent 5966a09c49
commit 27bedb2879
8 changed files with 68 additions and 2 deletions

View file

@ -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

View file

@ -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

View file

@ -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}`;
}

View file

@ -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 };

View file

@ -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;
}

View file

@ -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}`,
);
});
});

View file

@ -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 () => {

View file

@ -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 () => {