fix(cliproxy): harden custom local port handling

This commit is contained in:
Tam Nhu Tran committed 2026-05-03 14:07:31 -04:00
1 parent a7c10be72e
commit 3862411bb7
6 files changed
+155 -8

No files matched your search

@@ -66,6 +66,57 @@ describe('cursor-profile-executor', () => {
expect(warning).toBeNull();
});
it('starts local CLIProxy on the configured lifecycle port for cursor image analysis', async () => {
let ensuredPort: number | undefined;
const { env, warning } = await resolveCursorImageAnalysisEnv(false, {
getImageAnalysisHookEnv: () => ({
CCS_CURRENT_PROVIDER: 'ghcp',
CCS_IMAGE_ANALYSIS_SKIP: '0',
}),
resolveImageAnalysisRuntimeStatus: async () => ({
enabled: true,
supported: true,
status: 'active',
backendId: 'ghcp',
backendDisplayName: 'GitHub Copilot (OAuth)',
model: 'claude-haiku-4.5',
resolutionSource: 'cursor-alias',
reason: null,
shouldPersistHook: true,
persistencePath: 'cursor.settings.json',
runtimePath: '/api/provider/ghcp',
usesCurrentTarget: true,
usesCurrentAuthToken: true,
hookInstalled: true,
sharedHookInstalled: true,
authReadiness: 'ready',
authProvider: 'ghcp',
authDisplayName: 'GitHub Copilot (OAuth)',
authReason: null,
proxyReadiness: 'stopped',
proxyReason:
'Local CLIProxy service is idle. CCS will start it automatically when image analysis is needed.',
effectiveRuntimeMode: 'cliproxy-image-analysis',
effectiveRuntimeReason: null,
}),
ensureCliproxyService: async (port: number) => {
ensuredPort = port;
return {
started: true,
alreadyRunning: false,
port,
};
},
resolveLifecyclePort: () => 9321,
});
expect(ensuredPort).toBe(9321);
expect(env.CCS_CURRENT_PROVIDER).toBe('ghcp');
expect(env.CCS_IMAGE_ANALYSIS_SKIP).toBe('0');
expect(warning).toBeNull();
});
it('fails fast when Cursor integration is disabled', async () => {
const exitCode = await executeCursorProfile({ ...BASE_CONFIG, enabled: false }, []);
expect(exitCode).toBe(1);
@@ -6,6 +6,8 @@ import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { apiRoutes } from '../../../src/web-server/routes';
import { mutateConfig, loadOrCreateUnifiedConfig } from '../../../src/config/config-loader-facade';
import { registerSession, deleteSessionLockForPort } from '../../../src/cliproxy/session-tracker';
import {
authMiddleware,
createSessionMiddleware,
@@ -125,6 +127,54 @@ describe('api-routes remote write guard', () => {
});
});
it('rejects invalid local ports at the cliproxy-server API boundary', async () => {
forcedRemoteAddress = '127.0.0.1';
const response = await fetch(`${baseUrl}/api/cliproxy-server`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
local: { port: 70000 },
}),
});
expect(response.status).toBe(400);
expect(await response.json()).toEqual({
error: 'Invalid local port. Must be an integer between 1 and 65535.',
});
});
it('rejects local port changes while the current local proxy session is still running', async () => {
forcedRemoteAddress = '127.0.0.1';
mutateConfig((config) => {
if (!config.cliproxy_server) {
throw new Error('cliproxy_server defaults were not initialized');
}
config.cliproxy_server.local.port = 8317;
});
registerSession(8317, process.pid);
try {
const response = await fetch(`${baseUrl}/api/cliproxy-server`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
local: { port: 9000 },
}),
});
expect(response.status).toBe(409);
expect(await response.json()).toEqual({
error: 'Proxy is running on the current local port. Stop CLIProxy before changing local.port.',
proxyRunning: true,
currentLocalPort: 8317,
});
expect(loadOrCreateUnifiedConfig().cliproxy_server?.local?.port).toBe(8317);
} finally {
deleteSessionLockForPort(8317);
}
});
it('blocks remote PATCH requests when dashboard auth is disabled', async () => {
const response = await fetch(`${baseUrl}/api/codex/config/patch`, {
method: 'PATCH',