mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(daemon): allow codex sandbox override
This commit is contained in:
parent
fe58db2ba1
commit
cbb3d392c0
5 changed files with 132 additions and 53 deletions
|
|
@ -48,6 +48,10 @@ export function codexNeedsDangerFullAccessSandbox(
|
||||||
platform: NodeJS.Platform = process.platform,
|
platform: NodeJS.Platform = process.platform,
|
||||||
env: NodeJS.ProcessEnv = process.env,
|
env: NodeJS.ProcessEnv = process.env,
|
||||||
): boolean {
|
): 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;
|
if (platform === 'win32') return true;
|
||||||
// WSL reports `linux` but Codex still hits the Windows read-only
|
// WSL reports `linux` but Codex still hits the Windows read-only
|
||||||
// workspace-write sandbox path when launched from there (#2834).
|
// workspace-write sandbox path when launched from there (#2834).
|
||||||
|
|
|
||||||
|
|
@ -103,33 +103,37 @@ test('local agent profiles skip explicit unknown baseAgent without falling back'
|
||||||
});
|
});
|
||||||
|
|
||||||
test('codex args disable plugins when OD_CODEX_DISABLE_PLUGINS is 1', () => {
|
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', () => {
|
withPlatform('darwin', () => {
|
||||||
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
||||||
|
|
||||||
assert.deepEqual(args.slice(0, 11), [
|
assert.deepEqual(args.slice(0, 11), [
|
||||||
'exec',
|
'exec',
|
||||||
'--json',
|
'--json',
|
||||||
'--skip-git-repo-check',
|
'--skip-git-repo-check',
|
||||||
'--sandbox',
|
'--sandbox',
|
||||||
'workspace-write',
|
'workspace-write',
|
||||||
'-c',
|
'-c',
|
||||||
'sandbox_workspace_write.network_access=true',
|
'sandbox_workspace_write.network_access=true',
|
||||||
'-c',
|
'-c',
|
||||||
'default_permissions=":workspace"',
|
'default_permissions=":workspace"',
|
||||||
'--disable',
|
'--disable',
|
||||||
'plugins',
|
'plugins',
|
||||||
]);
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('codex args use workspace-write sandbox on macOS and Linux', () => {
|
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) {
|
for (const platform of ['darwin', 'linux'] as const) {
|
||||||
withPlatform(platform, () => {
|
withPlatform(platform, () => {
|
||||||
withEnvSnapshot(['WSL_DISTRO_NAME'], () => {
|
|
||||||
delete process.env.WSL_DISTRO_NAME;
|
delete process.env.WSL_DISTRO_NAME;
|
||||||
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
||||||
assert.equal(args.includes('--full-auto'), false);
|
assert.equal(args.includes('--full-auto'), false);
|
||||||
|
|
@ -149,15 +153,15 @@ test('codex args use workspace-write sandbox on macOS and Linux', () => {
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('codex args use danger-full-access sandbox on WSL because workspace-write stays read-only', () => {
|
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', () => {
|
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';
|
process.env.WSL_DISTRO_NAME = 'Ubuntu';
|
||||||
assert.equal(codexNeedsDangerFullAccessSandbox('linux', process.env), true);
|
assert.equal(codexNeedsDangerFullAccessSandbox('linux', process.env), true);
|
||||||
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
||||||
|
|
@ -173,6 +177,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', () => {
|
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
|
// Codex CLI's workspace-write sandbox mode on Windows lacks a working
|
||||||
// OS-level sandbox and falls back to a policy that rejects shell
|
// OS-level sandbox and falls back to a policy that rejects shell
|
||||||
|
|
@ -180,48 +228,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
|
// The agent cannot list files or run any shell-backed tool under that
|
||||||
// policy. danger-full-access is Codex CLI's documented Windows-compatible
|
// policy. danger-full-access is Codex CLI's documented Windows-compatible
|
||||||
// mode (issue #1721).
|
// 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', () => {
|
withPlatform('win32', () => {
|
||||||
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
||||||
|
|
||||||
assert.deepEqual(args.slice(0, 5), [
|
assert.deepEqual(args.slice(0, 5), [
|
||||||
'exec',
|
'exec',
|
||||||
'--json',
|
'--json',
|
||||||
'--skip-git-repo-check',
|
'--skip-git-repo-check',
|
||||||
'--sandbox',
|
'--sandbox',
|
||||||
'danger-full-access',
|
'danger-full-access',
|
||||||
]);
|
]);
|
||||||
// The workspace-write-scoped network override is meaningless under
|
// The workspace-write-scoped network override is meaningless under
|
||||||
// danger-full-access and must not appear on Windows.
|
// danger-full-access and must not appear on Windows.
|
||||||
assert.equal(args.includes('workspace-write'), false);
|
assert.equal(args.includes('workspace-write'), false);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
args.includes('sandbox_workspace_write.network_access=true'),
|
args.includes('sandbox_workspace_write.network_access=true'),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
assert.equal(args.includes('default_permissions=":workspace"'), true);
|
assert.equal(args.includes('default_permissions=":workspace"'), true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('codex args keep plugins enabled when OD_CODEX_DISABLE_PLUGINS is unset', () => {
|
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', () => {
|
withPlatform('darwin', () => {
|
||||||
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
||||||
|
|
||||||
assert.equal(args.includes('--disable'), false);
|
assert.equal(args.includes('--disable'), false);
|
||||||
assert.equal(args.includes('plugins'), false);
|
assert.equal(args.includes('plugins'), false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('codex args keep plugins enabled when OD_CODEX_DISABLE_PLUGINS is not 1', () => {
|
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', () => {
|
withPlatform('darwin', () => {
|
||||||
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
const args = codex.buildArgs('', [], [], {}, { cwd: '/tmp/od-project' });
|
||||||
|
|
||||||
assert.equal(args.includes('--disable'), false);
|
assert.equal(args.includes('--disable'), false);
|
||||||
assert.equal(args.includes('plugins'), false);
|
assert.equal(args.includes('plugins'), false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,6 @@ OPEN_DESIGN_MEM_LIMIT=384m
|
||||||
# Node.js heap cap inside the container.
|
# Node.js heap cap inside the container.
|
||||||
NODE_OPTIONS=--max-old-space-size=192
|
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=
|
||||||
|
|
|
||||||
|
|
@ -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
|
those outside the image, or build a separate private runtime layer if a server
|
||||||
deployment needs local code-agent CLIs installed in the container.
|
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
|
## Publish to Docker Hub
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ services:
|
||||||
OD_PORT: 7456
|
OD_PORT: 7456
|
||||||
OD_WEB_PORT: ${OPEN_DESIGN_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_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:
|
ports:
|
||||||
- "127.0.0.1:${OPEN_DESIGN_PORT:-7456}:7456"
|
- "127.0.0.1:${OPEN_DESIGN_PORT:-7456}:7456"
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue