fix(voice): start codex login without the server's provider secrets

The login child inherited the whole server environment, including
OPENAI_API_KEY and every other provider credential. Pass only what Codex
needs to find its home, open a browser and reach OpenAI through a proxy.
This commit is contained in:
Sameh Khamis committed 2026-10-08 11:02:12 -07:00
1 parent c99653047f
commit 3fa5bb7f5f
2 files changed
+31 -5

No files matched your search

+16 -1
View File
@@ -138,6 +138,21 @@ export function resolveCodexExecutable({
return 'codex'; return 'codex';
} }
// What `codex login` needs to find its home, open a browser and reach OpenAI.
// The server's own environment also holds every provider secret, so the
// child receives only these names.
const CODEX_LOGIN_ENV =
/^(?:PATH|PATHEXT|HOME|USER|USERNAME|LOGNAME|SHELL|LANG|LANGUAGE|LC_[A-Z_]+|TERM|TZ|TMPDIR|TEMP|TMP|XDG_[A-Z_]+|DISPLAY|WAYLAND_DISPLAY|DBUS_SESSION_BUS_ADDRESS|BROWSER|WSL_[A-Z_]+|WSLENV|(?:HTTPS?|ALL|NO)_PROXY|SSL_CERT_(?:FILE|DIR)|NODE_EXTRA_CA_CERTS|SYSTEMROOT|WINDIR|COMSPEC|USERPROFILE|APPDATA|LOCALAPPDATA|PROGRAMDATA|HOMEDRIVE|HOMEPATH|CODEX_[A-Z_]+)$/i;
/** The environment `codex login` runs with: the allowlist, then `CODEX_HOME`. */
export function codexLoginEnvironment(environment, codexHome) {
const env = {};
for (const [name, value] of Object.entries(environment || {}))
if (CODEX_LOGIN_ENV.test(name) && value !== undefined) env[name] = value;
env.CODEX_HOME = codexHome;
return env;
}
export function startCodexChatGptLogin({ export function startCodexChatGptLogin({
spawnImpl = spawn, spawnImpl = spawn,
environment = process.env, environment = process.env,
@@ -182,7 +197,7 @@ export function startCodexChatGptLogin({
cwd: home, cwd: home,
detached: true, detached: true,
stdio: 'ignore', stdio: 'ignore',
env: { ...environment, CODEX_HOME: loginHome }, env: codexLoginEnvironment(environment, loginHome),
}); });
} catch { } catch {
fail(); fail();
+15 -4
View File
@@ -168,7 +168,13 @@ test('Codex login resolves an installed executable before PATH and starts browse
const pending = startCodexChatGptLogin({ const pending = startCodexChatGptLogin({
executable: '/fixture/codex', executable: '/fixture/codex',
home: '/home/fixture', home: '/home/fixture',
environment: { PATH: '/fixture' }, environment: {
PATH: '/fixture',
https_proxy: 'http://proxy.fixture:8080',
OPENAI_API_KEY: 'fixture-api-key',
GOOGLE_MAPS_SERVER_API_KEY: 'fixture-google-key',
OPENSKY_CLIENT_SECRET: 'fixture-opensky-secret',
},
spawnImpl(command, args, options) { spawnImpl(command, args, options) {
calls.push({ command, args, options }); calls.push({ command, args, options });
queueMicrotask(() => child.emit('spawn')); queueMicrotask(() => child.emit('spawn'));
@@ -181,9 +187,14 @@ test('Codex login resolves an installed executable before PATH and starts browse
assert.deepEqual(calls[0].args, ['login']); assert.deepEqual(calls[0].args, ['login']);
assert.equal(calls[0].options.detached, true); assert.equal(calls[0].options.detached, true);
assert.equal(calls[0].options.stdio, 'ignore'); assert.equal(calls[0].options.stdio, 'ignore');
assert.equal( assert.deepEqual(
calls[0].options.env.CODEX_HOME, calls[0].options.env,
path.join('/home/fixture', '.codex'), {
PATH: '/fixture',
https_proxy: 'http://proxy.fixture:8080',
CODEX_HOME: path.join('/home/fixture', '.codex'),
},
'provider secrets stay out of the login process',
); );
child.emit('exit', 1); child.emit('exit', 1);
assert.deepEqual(await login.completion, { exitCode: 1 }); assert.deepEqual(await login.completion, { exitCode: 1 });