fix(proxy): avoid exposing local auth token in daemon argv (#1230)

This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-05-12 18:13:20 -04:00
1 parent 9c5d4976c5
commit a840793a9b
5 files changed
+160 -19

No files matched your search

+26 -3
View File
@@ -1,3 +1,4 @@
import * as fs from 'fs';
import { resolveOpenAICompatProfileConfig } from './profile-router';
import { OPENAI_COMPAT_PROXY_DEFAULT_PORT } from './proxy-daemon-paths';
import { startOpenAICompatProxyServer } from './server/proxy-server';
@@ -9,6 +10,7 @@ interface RuntimeOptions {
profileName: string;
settingsPath: string;
authToken: string;
authTokenFile: string;
insecure: boolean;
}
@@ -18,6 +20,7 @@ function parseArgs(argv: string[]): RuntimeOptions {
let profileName = '';
let settingsPath = '';
let authToken = '';
let authTokenFile = '';
let insecure = false;
for (let i = 0; i < argv.length; i++) {
@@ -42,16 +45,36 @@ function parseArgs(argv: string[]): RuntimeOptions {
authToken = argv[++i] || '';
continue;
}
if (arg === '--auth-token-file' && argv[i + 1]) {
authTokenFile = argv[++i] || '';
continue;
}
if (arg === '--insecure') {
insecure = true;
}
}
return { port, host, profileName, settingsPath, authToken, insecure };
return { port, host, profileName, settingsPath, authToken, authTokenFile, insecure };
}
function readAuthToken(options: RuntimeOptions): string {
if (options.authTokenFile) {
try {
const token = fs.readFileSync(options.authTokenFile, 'utf8').trim();
fs.unlinkSync(options.authTokenFile);
return token;
} catch (error) {
throw new Error(`Failed to read local proxy auth token file: ${(error as Error).message}`);
}
}
return options.authToken;
}
function startRuntime(options: RuntimeOptions): void {
if (!options.authToken.trim()) {
const authToken = readAuthToken(options);
if (!authToken.trim()) {
throw new Error('Missing local proxy auth token');
}
@@ -71,7 +94,7 @@ function startRuntime(options: RuntimeOptions): void {
profile,
host: options.host,
port: options.port,
authToken: options.authToken,
authToken,
insecure: options.insecure,
});
server.once('error', (error) => {
+46 -7
View File
@@ -1,3 +1,4 @@
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import {
@@ -20,7 +21,40 @@ export interface OpenAICompatProxySession {
}
function ensureProxyDir(): void {
fs.mkdirSync(getOpenAICompatProxyDir(), { recursive: true });
const proxyDir = getOpenAICompatProxyDir();
fs.mkdirSync(proxyDir, { recursive: true, mode: 0o700 });
fs.chmodSync(proxyDir, 0o700);
}
function encodeProfileFileKey(profileName: string): string {
return encodeURIComponent(profileName);
}
export function writeOpenAICompatProxyAuthTokenFile(
profileName: string,
authToken: string
): string {
ensureProxyDir();
const tokenPath = path.join(
getOpenAICompatProxyDir(),
`.${encodeProfileFileKey(profileName)}.${crypto.randomBytes(8).toString('hex')}.token`
);
const fd = fs.openSync(tokenPath, 'wx', 0o600);
try {
fs.writeFileSync(fd, authToken, 'utf8');
} finally {
fs.closeSync(fd);
}
fs.chmodSync(tokenPath, 0o600);
return tokenPath;
}
export function removeOpenAICompatProxyAuthTokenFile(tokenPath: string): void {
try {
fs.unlinkSync(tokenPath);
} catch {
// Best-effort cleanup.
}
}
function readPid(pidPath: string): number | null {
@@ -43,7 +77,11 @@ export function getLegacyOpenAICompatProxyPid(): number | null {
export function writeOpenAICompatProxyPid(profileName: string, pid: number): void {
ensureProxyDir();
fs.writeFileSync(getOpenAICompatProxyPidPath(profileName), String(pid), 'utf8');
fs.writeFileSync(getOpenAICompatProxyPidPath(profileName), String(pid), {
encoding: 'utf8',
mode: 0o600,
});
fs.chmodSync(getOpenAICompatProxyPidPath(profileName), 0o600);
}
export function removeOpenAICompatProxyPid(profileName: string): void {
@@ -80,11 +118,12 @@ export function readLegacyOpenAICompatProxySession(): OpenAICompatProxySession |
export function writeOpenAICompatProxySession(session: OpenAICompatProxySession): void {
ensureProxyDir();
fs.writeFileSync(
getOpenAICompatProxySessionPath(session.profileName),
JSON.stringify(session, null, 2) + '\n',
'utf8'
);
const sessionPath = getOpenAICompatProxySessionPath(session.profileName);
fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2) + '\n', {
encoding: 'utf8',
mode: 0o600,
});
fs.chmodSync(sessionPath, 0o600);
}
export function removeOpenAICompatProxySession(profileName: string): void {
+13 -6
View File
@@ -19,10 +19,12 @@ import {
readOpenAICompatProxySession,
removeLegacyOpenAICompatProxyPid,
removeLegacyOpenAICompatProxySession,
removeOpenAICompatProxyAuthTokenFile,
removeOpenAICompatProxyPid,
removeOpenAICompatProxySession,
resolveOpenAICompatProxyEntrypointCandidates,
type OpenAICompatProxySession,
writeOpenAICompatProxyAuthTokenFile,
writeOpenAICompatProxyPid,
writeOpenAICompatProxySession,
} from './proxy-daemon-state';
@@ -82,7 +84,8 @@ function generateProxyAuthToken(): string {
async function withOpenAICompatProxyLock<T>(operation: () => Promise<T>): Promise<T> {
const proxyDir = getOpenAICompatProxyDir();
await fs.promises.mkdir(proxyDir, { recursive: true });
await fs.promises.mkdir(proxyDir, { recursive: true, mode: 0o700 });
await fs.promises.chmod(proxyDir, 0o700);
let release: (() => Promise<void>) | undefined;
try {
@@ -543,6 +546,7 @@ export async function startOpenAICompatProxy(
let timeout: NodeJS.Timeout | null = null;
let stderr = '';
const authToken = generateProxyAuthToken();
const authTokenFile = writeOpenAICompatProxyAuthTokenFile(profile.profileName, authToken);
const commitState = () => {
if (proc.pid) {
writeOpenAICompatProxyPid(profile.profileName, proc.pid);
@@ -563,9 +567,12 @@ export async function startOpenAICompatProxy(
if (resolved) return;
resolved = true;
if (timeout) clearTimeout(timeout);
if (!result.success && persistState) {
removeOpenAICompatProxyPid(profile.profileName);
removeOpenAICompatProxySession(profile.profileName);
if (!result.success) {
removeOpenAICompatProxyAuthTokenFile(authTokenFile);
if (persistState) {
removeOpenAICompatProxyPid(profile.profileName);
removeOpenAICompatProxySession(profile.profileName);
}
}
resolve(result);
};
@@ -582,8 +589,8 @@ export async function startOpenAICompatProxy(
profile.profileName,
'--settings-path',
profile.settingsPath,
'--auth-token',
authToken,
'--auth-token-file',
authTokenFile,
...(options.insecure ? ['--insecure'] : []),
'--ccs-openai-proxy-daemon',
],