Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions packages/cli/src/utils/sandbox.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/**
* @license
* Copyright 2025 Vybestack LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect, beforeEach } from 'vitest';
import { buildSandboxEnvArgs, getPassthroughEnvVars } from './sandbox.js';

describe('getPassthroughEnvVars', () => {
let mockEnv: NodeJS.ProcessEnv;

beforeEach(() => {
mockEnv = {
PATH: '/usr/bin:/bin',
HOME: '/home/user',
LLXPRT_CODE_IDE_SERVER_PORT: '3000',
LLXPRT_CODE_IDE_WORKSPACE_PATH: '/workspace',
LLXPRT_CODE_WELCOME_CONFIG_PATH: '/config.json',
TERM_PROGRAM: 'VSCode',
NODE_ENV: 'test',
};
});

it('should return empty object when no passthrough vars are set', () => {
const env: NodeJS.ProcessEnv = {
PATH: '/usr/bin',
HOME: '/home/user',
};

const result = getPassthroughEnvVars(env);

expect(result).toEqual({});
});

it('should return LLXPRT_CODE_IDE_SERVER_PORT when set', () => {
const result = getPassthroughEnvVars(mockEnv);

expect(result).toHaveProperty('LLXPRT_CODE_IDE_SERVER_PORT', '3000');
});

it('should return LLXPRT_CODE_IDE_WORKSPACE_PATH when set', () => {
const result = getPassthroughEnvVars(mockEnv);

expect(result).toHaveProperty(
'LLXPRT_CODE_IDE_WORKSPACE_PATH',
'/workspace',
);
});

it('should return LLXPRT_CODE_WELCOME_CONFIG_PATH when set', () => {
const result = getPassthroughEnvVars(mockEnv);

expect(result).toHaveProperty(
'LLXPRT_CODE_WELCOME_CONFIG_PATH',
'/config.json',
);
});

it('should return TERM_PROGRAM when set', () => {
const result = getPassthroughEnvVars(mockEnv);

expect(result).toHaveProperty('TERM_PROGRAM', 'VSCode');
});

it('should return all passthrough variables when all are set', () => {
const result = getPassthroughEnvVars(mockEnv);

expect(result).toEqual({
LLXPRT_CODE_IDE_SERVER_PORT: '3000',
LLXPRT_CODE_IDE_WORKSPACE_PATH: '/workspace',
LLXPRT_CODE_WELCOME_CONFIG_PATH: '/config.json',
TERM_PROGRAM: 'VSCode',
});
});

it('should ignore passthrough vars with empty string values', () => {
const env: NodeJS.ProcessEnv = {
LLXPRT_CODE_IDE_SERVER_PORT: '',
LLXPRT_CODE_IDE_WORKSPACE_PATH: '/workspace',
TERM_PROGRAM: 'VSCode',
};

const result = getPassthroughEnvVars(env);

expect(result).toEqual({
LLXPRT_CODE_IDE_WORKSPACE_PATH: '/workspace',
TERM_PROGRAM: 'VSCode',
});
});

it('should ignore passthrough vars with undefined values', () => {
const env: NodeJS.ProcessEnv = {
LLXPRT_CODE_IDE_SERVER_PORT: undefined,
LLXPRT_CODE_IDE_WORKSPACE_PATH: '/workspace',
TERM_PROGRAM: 'VSCode',
};

const result = getPassthroughEnvVars(env);

expect(result).toEqual({
LLXPRT_CODE_IDE_WORKSPACE_PATH: '/workspace',
TERM_PROGRAM: 'VSCode',
});
});

it('should handle special characters in variable values', () => {
const env: NodeJS.ProcessEnv = {
LLXPRT_CODE_IDE_SERVER_PORT: '3000:3001',
LLXPRT_CODE_IDE_WORKSPACE_PATH: '/path/to/workspace with spaces',
TERM_PROGRAM: 'iTerm.app',
};

const result = getPassthroughEnvVars(env);

expect(result).toEqual({
LLXPRT_CODE_IDE_SERVER_PORT: '3000:3001',
LLXPRT_CODE_IDE_WORKSPACE_PATH: '/path/to/workspace with spaces',
TERM_PROGRAM: 'iTerm.app',
});
});

it('should not mutate the input object', () => {
const env: NodeJS.ProcessEnv = { ...mockEnv };
getPassthroughEnvVars(env);

expect(env).toEqual(mockEnv);
});

it('should return a new object', () => {
const env: NodeJS.ProcessEnv = { ...mockEnv };
const result = getPassthroughEnvVars(env);

expect(result).not.toBe(env);
});
});

describe('buildSandboxEnvArgs', () => {
let mockEnv: NodeJS.ProcessEnv;

beforeEach(() => {
mockEnv = {
PATH: '/usr/bin:/bin',
HOME: '/home/user',
LLXPRT_CODE_IDE_SERVER_PORT: '3000',
LLXPRT_CODE_IDE_WORKSPACE_PATH: '/workspace',
LLXPRT_CODE_WELCOME_CONFIG_PATH: '/config.json',
TERM_PROGRAM: 'VSCode',
NODE_ENV: 'test',
};
});

it('should return empty array when no passthrough vars are set', () => {
const env: NodeJS.ProcessEnv = {
PATH: '/usr/bin',
HOME: '/home/user',
};

const result = buildSandboxEnvArgs(env);

expect(result).toEqual([]);
});

it('should format passthrough vars as CLI args with --env prefix', () => {
const result = buildSandboxEnvArgs({
LLXPRT_CODE_IDE_SERVER_PORT: '3000',
TERM_PROGRAM: 'VSCode',
});

expect(result).toEqual([
'--env',
'LLXPRT_CODE_IDE_SERVER_PORT=3000',
'--env',
'TERM_PROGRAM=VSCode',
]);
});

it('should include all set passthrough variables', () => {
const result = buildSandboxEnvArgs(mockEnv);

expect(result).toEqual([
'--env',
'LLXPRT_CODE_IDE_SERVER_PORT=3000',
'--env',
'LLXPRT_CODE_IDE_WORKSPACE_PATH=/workspace',
'--env',
'LLXPRT_CODE_WELCOME_CONFIG_PATH=/config.json',
'--env',
'TERM_PROGRAM=VSCode',
]);
});

it('should handle values containing equals signs', () => {
const result = buildSandboxEnvArgs({
LLXPRT_CODE_IDE_SERVER_PORT: '3000=3001',
});

expect(result).toEqual(['--env', 'LLXPRT_CODE_IDE_SERVER_PORT=3000=3001']);
});

it('should handle values with spaces', () => {
const result = buildSandboxEnvArgs({
LLXPRT_CODE_IDE_WORKSPACE_PATH: '/path/to/workspace with spaces',
});

expect(result).toEqual([
'--env',
'LLXPRT_CODE_IDE_WORKSPACE_PATH=/path/to/workspace with spaces',
]);
});

it('should not mutate the input object', () => {
const env: NodeJS.ProcessEnv = { ...mockEnv };
const originalEnv = { ...env };
buildSandboxEnvArgs(env);

expect(env).toEqual(originalEnv);
});

it('should return a new array', () => {
const env: NodeJS.ProcessEnv = { ...mockEnv };
const result1 = buildSandboxEnvArgs(env);
const result2 = buildSandboxEnvArgs(env);

expect(result2).not.toBe(result1);
});
});
48 changes: 27 additions & 21 deletions packages/cli/src/utils/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,33 @@ const BUILTIN_SEATBELT_PROFILES = [
'restrictive-proxied',
];

export function buildSandboxEnvArgs(env: NodeJS.ProcessEnv): string[] {
const args: string[] = [];
const passthroughVariables = [
'LLXPRT_CODE_IDE_SERVER_PORT',
'LLXPRT_CODE_IDE_WORKSPACE_PATH',
'LLXPRT_CODE_WELCOME_CONFIG_PATH',
'TERM_PROGRAM',
];

for (const envVar of passthroughVariables) {
const PASSTHROUGH_VARIABLES = [
'LLXPRT_CODE_IDE_SERVER_PORT',
'LLXPRT_CODE_IDE_WORKSPACE_PATH',
'LLXPRT_CODE_WELCOME_CONFIG_PATH',
'TERM_PROGRAM',
] as const;

export function getPassthroughEnvVars(
env: NodeJS.ProcessEnv,
): Record<string, string> {
const result: Record<string, string> = {};

for (const envVar of PASSTHROUGH_VARIABLES) {
const value = env[envVar];
if (value) {
args.push('--env', `${envVar}=${value}`);
if (typeof value === 'string' && value.length > 0) {
result[envVar] = value;
}
}

return args;
return result;
}

export function buildSandboxEnvArgs(env: NodeJS.ProcessEnv): string[] {
return Object.entries(getPassthroughEnvVars(env)).flatMap(([key, value]) => [
'--env',
`${key}=${value}`,
]);
}

/**
Expand Down Expand Up @@ -330,14 +340,10 @@ export async function start_sandbox(
const proxyCommand = process.env.LLXPRT_SANDBOX_PROXY_COMMAND;
let proxyProcess: ChildProcess | undefined = undefined;
let sandboxProcess: ChildProcess | undefined = undefined;
const sandboxEnv = { ...process.env };
for (const envVar of buildSandboxEnvArgs(process.env)) {
if (envVar === '--env') {
continue;
}
const [key, ...rest] = envVar.split('=');
sandboxEnv[key] = rest.join('=');
}
const sandboxEnv = {
...process.env,
};
Object.assign(sandboxEnv, getPassthroughEnvVars(process.env));

Comment on lines +343 to 347
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Run the verification queries from the review comment
echo "=== Searching for sandbox-exec usage ==="
rg -n "sandbox-exec" -C3

echo -e "\n=== Searching for sandboxEnv assignment ==="
rg -n "sandboxEnv\s*=\s*\{" -C3

echo -e "\n=== Searching for getPassthroughEnvVars calls ==="
rg -n "getPassthroughEnvVars\(" -C3

Repository: vybestack/llxprt-code

Length of output: 28763


🏁 Script executed:

#!/bin/bash
# Get the full context of the function containing lines 343-347
echo "=== Full context around lines 343-347 in sandbox.ts ==="
sed -n '320,370p' packages/cli/src/utils/sandbox.ts

Repository: vybestack/llxprt-code

Length of output: 1788


🏁 Script executed:

#!/bin/bash
# Find and inspect the getPassthroughEnvVars function
echo "=== Finding getPassthroughEnvVars implementation ==="
rg -n "function getPassthroughEnvVars|const getPassthroughEnvVars|export.*getPassthroughEnvVars" -A 10

Repository: vybestack/llxprt-code

Length of output: 839


Object.assign with getPassthroughEnvVars is functionally redundant; consider building sandboxEnv from curated vars only.

Starting sandboxEnv with { ...process.env } includes all environment variables. The subsequent Object.assign(sandboxEnv, getPassthroughEnvVars(process.env)) overwrites already-present values with identical ones, but doesn't filter out unwanted vars from the spread. If the goal is to pass only curated variables to sandbox-exec (as the helper's name suggests), construct sandboxEnv from getPassthroughEnvVars() first, then add proxy/control vars conditionally:

const sandboxEnv = getPassthroughEnvVars(process.env);
if (proxyCommand) {
  sandboxEnv['HTTPS_PROXY'] = proxy;
  // ... rest of proxy vars
}
🤖 Prompt for AI Agents
In @packages/cli/src/utils/sandbox.ts around lines 343 - 347, The sandboxEnv
currently spreads all of process.env then assigns
getPassthroughEnvVars(process.env), which is redundant and exposes unwanted
vars; change it to initialize sandboxEnv from getPassthroughEnvVars(process.env)
(i.e., const sandboxEnv = getPassthroughEnvVars(process.env)) and then add any
proxy/control variables (e.g., HTTPS_PROXY, HTTP_PROXY, NO_PROXY, or other vars
set when proxyCommand is present) conditionally to sandboxEnv so only curated
environment variables are passed into the sandbox.

if (proxyCommand) {
const proxy =
Expand Down
Loading