This commit is contained in:
Yuhao Chen 2026-05-31 01:23:31 -04:00 committed by GitHub
commit aeb73e79d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 132 additions and 53 deletions

View file

@ -48,6 +48,10 @@ export function codexNeedsDangerFullAccessSandbox(
platform: NodeJS.Platform = process.platform,
env: NodeJS.ProcessEnv = process.env,
): boolean {
// Operator override for deployments where Codex cannot create its
// workspace-write sandbox, for example unprivileged Linux containers.
// Only danger-full-access is accepted; unknown values keep the default path.
if (env.OD_CODEX_SANDBOX?.trim() === 'danger-full-access') return true;
if (platform === 'win32') return true;
// WSL reports `linux` but Codex still hits the Windows read-only
// workspace-write sandbox path when launched from there (#2834).

View file

@ -128,33 +128,37 @@ test('sandbox mode ignores implicit and host explicit local agent profiles', asy
});
test('codex args disable plugins when OD_CODEX_DISABLE_PLUGINS is 1', () => {
process.env.OD_CODEX_DISABLE_PLUGINS = '1';
withEnvSnapshot(['OD_CODEX_DISABLE_PLUGINS', 'OD_CODEX_SANDBOX'], () => {
process.env.OD_CODEX_DISABLE_PLUGINS = '1';
delete process.env.OD_CODEX_SANDBOX;
withPlatform('darwin', () => {
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
withPlatform('darwin', () => {
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
assert.deepEqual(args.slice(0, 11), [
'exec',
'--json',
'--skip-git-repo-check',
'--sandbox',
'workspace-write',
'-c',
'sandbox_workspace_write.network_access=true',
'-c',
'default_permissions=":workspace"',
'--disable',
'plugins',
]);
assert.deepEqual(args.slice(0, 11), [
'exec',
'--json',
'--skip-git-repo-check',
'--sandbox',
'workspace-write',
'-c',
'sandbox_workspace_write.network_access=true',
'-c',
'default_permissions=":workspace"',
'--disable',
'plugins',
]);
});
});
});
test('codex args use workspace-write sandbox on macOS and Linux', () => {
delete process.env.OD_CODEX_DISABLE_PLUGINS;
withEnvSnapshot(['OD_CODEX_DISABLE_PLUGINS', 'OD_CODEX_SANDBOX', 'WSL_DISTRO_NAME'], () => {
delete process.env.OD_CODEX_DISABLE_PLUGINS;
delete process.env.OD_CODEX_SANDBOX;
for (const platform of ['darwin', 'linux'] as const) {
withPlatform(platform, () => {
withEnvSnapshot(['WSL_DISTRO_NAME'], () => {
for (const platform of ['darwin', 'linux'] as const) {
withPlatform(platform, () => {
delete process.env.WSL_DISTRO_NAME;
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
assert.equal(args.includes('--full-auto'), false);
@ -174,15 +178,15 @@ test('codex args use workspace-write sandbox on macOS and Linux', () => {
true,
);
});
});
}
}
});
});
test('codex args use danger-full-access sandbox on WSL because workspace-write stays read-only', () => {
delete process.env.OD_CODEX_DISABLE_PLUGINS;
withPlatform('linux', () => {
withEnvSnapshot(['WSL_DISTRO_NAME'], () => {
withEnvSnapshot(['OD_CODEX_DISABLE_PLUGINS', 'OD_CODEX_SANDBOX', 'WSL_DISTRO_NAME'], () => {
delete process.env.OD_CODEX_DISABLE_PLUGINS;
delete process.env.OD_CODEX_SANDBOX;
process.env.WSL_DISTRO_NAME = 'Ubuntu';
assert.equal(codexNeedsDangerFullAccessSandbox('linux', process.env), true);
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
@ -198,6 +202,50 @@ test('codex args use danger-full-access sandbox on WSL because workspace-write s
});
});
test('codex args allow OD_CODEX_SANDBOX danger-full-access override on Linux', () => {
withPlatform('linux', () => {
withEnvSnapshot(['OD_CODEX_DISABLE_PLUGINS', 'OD_CODEX_SANDBOX', 'WSL_DISTRO_NAME'], () => {
delete process.env.OD_CODEX_DISABLE_PLUGINS;
process.env.OD_CODEX_SANDBOX = 'danger-full-access';
delete process.env.WSL_DISTRO_NAME;
assert.equal(codexNeedsDangerFullAccessSandbox('linux', process.env), true);
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
assert.deepEqual(args.slice(0, 5), [
'exec',
'--json',
'--skip-git-repo-check',
'--sandbox',
'danger-full-access',
]);
assert.equal(
args.includes('sandbox_workspace_write.network_access=true'),
false,
);
});
});
});
test('codex args ignore unknown OD_CODEX_SANDBOX values', () => {
withPlatform('linux', () => {
withEnvSnapshot(['OD_CODEX_DISABLE_PLUGINS', 'OD_CODEX_SANDBOX', 'WSL_DISTRO_NAME'], () => {
delete process.env.OD_CODEX_DISABLE_PLUGINS;
process.env.OD_CODEX_SANDBOX = 'workspace-write';
delete process.env.WSL_DISTRO_NAME;
assert.equal(codexNeedsDangerFullAccessSandbox('linux', process.env), false);
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
assert.deepEqual(args.slice(0, 5), [
'exec',
'--json',
'--skip-git-repo-check',
'--sandbox',
'workspace-write',
]);
});
});
});
test('codex args use danger-full-access sandbox on Windows because workspace-write blocks PowerShell', () => {
// Codex CLI's workspace-write sandbox mode on Windows lacks a working
// OS-level sandbox and falls back to a policy that rejects shell
@ -205,48 +253,57 @@ test('codex args use danger-full-access sandbox on Windows because workspace-wri
// The agent cannot list files or run any shell-backed tool under that
// policy. danger-full-access is Codex CLI's documented Windows-compatible
// mode (issue #1721).
delete process.env.OD_CODEX_DISABLE_PLUGINS;
withEnvSnapshot(['OD_CODEX_DISABLE_PLUGINS', 'OD_CODEX_SANDBOX'], () => {
delete process.env.OD_CODEX_DISABLE_PLUGINS;
delete process.env.OD_CODEX_SANDBOX;
withPlatform('win32', () => {
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
withPlatform('win32', () => {
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
assert.deepEqual(args.slice(0, 5), [
'exec',
'--json',
'--skip-git-repo-check',
'--sandbox',
'danger-full-access',
]);
// The workspace-write-scoped network override is meaningless under
// danger-full-access and must not appear on Windows.
assert.equal(args.includes('workspace-write'), false);
assert.equal(
args.includes('sandbox_workspace_write.network_access=true'),
false,
);
assert.equal(args.includes('default_permissions=":workspace"'), true);
assert.deepEqual(args.slice(0, 5), [
'exec',
'--json',
'--skip-git-repo-check',
'--sandbox',
'danger-full-access',
]);
// The workspace-write-scoped network override is meaningless under
// danger-full-access and must not appear on Windows.
assert.equal(args.includes('workspace-write'), false);
assert.equal(
args.includes('sandbox_workspace_write.network_access=true'),
false,
);
assert.equal(args.includes('default_permissions=":workspace"'), true);
});
});
});
test('codex args keep plugins enabled when OD_CODEX_DISABLE_PLUGINS is unset', () => {
delete process.env.OD_CODEX_DISABLE_PLUGINS;
withEnvSnapshot(['OD_CODEX_DISABLE_PLUGINS', 'OD_CODEX_SANDBOX'], () => {
delete process.env.OD_CODEX_DISABLE_PLUGINS;
delete process.env.OD_CODEX_SANDBOX;
withPlatform('darwin', () => {
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
withPlatform('darwin', () => {
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
assert.equal(args.includes('--disable'), false);
assert.equal(args.includes('plugins'), false);
assert.equal(args.includes('--disable'), false);
assert.equal(args.includes('plugins'), false);
});
});
});
test('codex args keep plugins enabled when OD_CODEX_DISABLE_PLUGINS is not 1', () => {
process.env.OD_CODEX_DISABLE_PLUGINS = 'true';
withEnvSnapshot(['OD_CODEX_DISABLE_PLUGINS', 'OD_CODEX_SANDBOX'], () => {
process.env.OD_CODEX_DISABLE_PLUGINS = 'true';
delete process.env.OD_CODEX_SANDBOX;
withPlatform('darwin', () => {
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
withPlatform('darwin', () => {
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
assert.equal(args.includes('--disable'), false);
assert.equal(args.includes('plugins'), false);
assert.equal(args.includes('--disable'), false);
assert.equal(args.includes('plugins'), false);
});
});
});

View file

@ -22,3 +22,6 @@ OPEN_DESIGN_MEM_LIMIT=384m
# Node.js heap cap inside the container.
NODE_OPTIONS=--max-old-space-size=192
# Optional Codex CLI sandbox override. Set to danger-full-access only when
# Codex fails with workspace-write sandbox setup errors inside the container.
OD_CODEX_SANDBOX=

View file

@ -58,6 +58,20 @@ The image intentionally does not bundle Claude/Codex/Gemini CLI binaries. Keep
those outside the image, or build a separate private runtime layer if a server
deployment needs local code-agent CLIs installed in the container.
If you install Codex inside an unprivileged Linux container and it fails while
creating its `workspace-write` sandbox, opt into Codex's full-access mode for
all Codex runs in that deployment:
```bash
OD_CODEX_SANDBOX=danger-full-access docker compose up -d --no-build
```
Only the exact value `danger-full-access` is supported; unknown values are
ignored. Use this only for trusted, single-user deployments. It lets Codex run
without the workspace-write sandbox, which is useful when the container host
blocks unprivileged user namespaces, but it gives the Codex process broader
filesystem access inside the container.
## Publish to Docker Hub
```bash

View file

@ -16,6 +16,7 @@ services:
OD_PORT: 7456
OD_WEB_PORT: ${OPEN_DESIGN_PORT:-7456}
OD_API_TOKEN: ${OD_API_TOKEN:?Please run 'openssl rand -hex 32' to generate one, and set it in your .env file.}
OD_CODEX_SANDBOX: ${OD_CODEX_SANDBOX:-}
ports:
- "127.0.0.1:${OPEN_DESIGN_PORT:-7456}:7456"
volumes: