mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-18 16:13:35 +00:00
chore: merge origin/dev into codex auth profile branch
This commit is contained in:
commit
ea421c4a20
14 files changed
+344
-45
No files matched your search
@@ -131,6 +131,15 @@ function runHookWithMockedFetch(mode: 'success' | 'empty' | 'non-result' | 'fail
|
||||
}
|
||||
}
|
||||
|
||||
describe('websearch-transformer legacy CLI safety', () => {
|
||||
it('does not enable shell execution for query-derived legacy CLI prompts', () => {
|
||||
const source = readFileSync(hookPath, 'utf8');
|
||||
|
||||
expect(source).not.toContain('shell: isWindows');
|
||||
expect(source.match(/shell: false/g) || []).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('websearch-transformer hook helpers', () => {
|
||||
it('parses Retry-After seconds and HTTP dates', () => {
|
||||
expect(hook.parseRetryAfterSeconds('2')).toBe(2);
|
||||
|
||||
@@ -78,19 +78,19 @@ wire_api = "responses"
|
||||
expect(result.changed).toBe(true);
|
||||
expect(result.envKey).toBe('CLIPROXY_API_KEY');
|
||||
const rawText = fs.readFileSync(configPath, 'utf8');
|
||||
expect(rawText).toContain(`base_url = "${buildCodexCliproxyProviderBaseUrl(9321)}"`);
|
||||
expect(rawText).toContain('base_url = "http://localhost:8317/api/provider/codex"');
|
||||
expect(rawText).toContain('env_key = "CLIPROXY_API_KEY"');
|
||||
expect(rawText).toContain('requires_openai_auth = false');
|
||||
expect(rawText).toContain('supports_websockets = false');
|
||||
});
|
||||
|
||||
it('preserves a custom cliproxy provider env key while repairing other fields', async () => {
|
||||
it('preserves custom cliproxy provider values while repairing other fields', async () => {
|
||||
fs.mkdirSync(codexHome, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
configPath,
|
||||
`[model_providers.cliproxy]
|
||||
name = "Old Name"
|
||||
base_url = "http://localhost:8317/api/provider/codex"
|
||||
base_url = "https://cliproxy.example.com/api/provider/codex/responses"
|
||||
env_key = "CCS_CUSTOM_CLIPROXY_TOKEN"
|
||||
wire_api = "chat"
|
||||
`,
|
||||
@@ -102,11 +102,35 @@ wire_api = "chat"
|
||||
expect(result.changed).toBe(true);
|
||||
expect(result.envKey).toBe('CCS_CUSTOM_CLIPROXY_TOKEN');
|
||||
const rawText = fs.readFileSync(configPath, 'utf8');
|
||||
expect(rawText).toContain(`base_url = "${buildCodexCliproxyProviderBaseUrl(9321)}"`);
|
||||
expect(rawText).toContain(
|
||||
'base_url = "https://cliproxy.example.com/api/provider/codex/responses"'
|
||||
);
|
||||
expect(rawText).toContain('env_key = "CCS_CUSTOM_CLIPROXY_TOKEN"');
|
||||
expect(rawText).toContain('wire_api = "responses"');
|
||||
});
|
||||
|
||||
it('repairs invalid cliproxy provider base URLs back to the managed local default', async () => {
|
||||
fs.mkdirSync(codexHome, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
configPath,
|
||||
`[model_providers.cliproxy]
|
||||
name = "CLIProxy Codex"
|
||||
base_url = "not-a-url"
|
||||
env_key = "CLIPROXY_API_KEY"
|
||||
wire_api = "responses"
|
||||
requires_openai_auth = false
|
||||
supports_websockets = false
|
||||
`,
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const result = await ensureCodexCliproxyProviderConfig(9321, env);
|
||||
|
||||
expect(result.changed).toBe(true);
|
||||
const rawText = fs.readFileSync(configPath, 'utf8');
|
||||
expect(rawText).toContain(`base_url = "${buildCodexCliproxyProviderBaseUrl(9321)}"`);
|
||||
});
|
||||
|
||||
it('rejects invalid non-table model_providers values without appending broken TOML', async () => {
|
||||
fs.mkdirSync(codexHome, { recursive: true });
|
||||
const rawText = 'model_providers = "legacy"\n';
|
||||
@@ -137,6 +161,25 @@ supports_websockets = false
|
||||
expect(fs.readFileSync(configPath, 'utf8')).toBe(rawText);
|
||||
});
|
||||
|
||||
it('leaves a ready remote provider unchanged', async () => {
|
||||
fs.mkdirSync(codexHome, { recursive: true });
|
||||
const rawText = `[model_providers.cliproxy]
|
||||
name = "CLIProxy Codex"
|
||||
base_url = "https://cliproxy.example.com/api/provider/codex"
|
||||
env_key = "CCS_REMOTE_CLIPROXY_TOKEN"
|
||||
wire_api = "responses"
|
||||
requires_openai_auth = false
|
||||
supports_websockets = false
|
||||
`;
|
||||
fs.writeFileSync(configPath, rawText, 'utf8');
|
||||
|
||||
const result = await ensureCodexCliproxyProviderConfig(8317, env);
|
||||
|
||||
expect(result.changed).toBe(false);
|
||||
expect(result.envKey).toBe('CCS_REMOTE_CLIPROXY_TOKEN');
|
||||
expect(fs.readFileSync(configPath, 'utf8')).toBe(rawText);
|
||||
});
|
||||
|
||||
it('normalizes a ready native Codex tuning alias before requests reach cliproxy', async () => {
|
||||
fs.mkdirSync(codexHome, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
|
||||
@@ -828,6 +828,60 @@ supports_websockets = false
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves a custom cliproxy provider base_url for ccsxp launches', () => {
|
||||
if (process.platform === 'win32') return;
|
||||
|
||||
const codexHome = path.join(tmpHome, '.codex');
|
||||
fs.mkdirSync(codexHome, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(codexHome, 'config.toml'),
|
||||
`[model_providers.cliproxy]
|
||||
name = "CLIProxy Codex"
|
||||
base_url = "https://cliproxy.example.com/api/provider/codex/responses"
|
||||
env_key = "CCS_REMOTE_CLIPROXY_TOKEN"
|
||||
wire_api = "responses"
|
||||
requires_openai_auth = false
|
||||
supports_websockets = false
|
||||
`,
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const result = runCcsxpAlias(['fix failing tests'], {
|
||||
...process.env,
|
||||
CI: '1',
|
||||
NO_COLOR: '1',
|
||||
HOME: tmpHome,
|
||||
CCS_HOME: tmpHome,
|
||||
CCS_CODEX_PATH: fakeCodexPath,
|
||||
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
|
||||
CCS_TEST_CODEX_ENV_OUT: codexEnvLogPath,
|
||||
CCS_TEST_CODEX_LOG_ENV_KEYS: 'CCS_REMOTE_CLIPROXY_TOKEN',
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(readLoggedCodexCalls(codexArgsLogPath)).toEqual([
|
||||
['--config', 'model_provider="cliproxy"', 'fix failing tests'],
|
||||
]);
|
||||
const codexConfig = fs.readFileSync(path.join(codexHome, 'config.toml'), 'utf8');
|
||||
expect(codexConfig).toContain(
|
||||
'base_url = "https://cliproxy.example.com/api/provider/codex/responses"'
|
||||
);
|
||||
expect(codexConfig).toContain('env_key = "CCS_REMOTE_CLIPROXY_TOKEN"');
|
||||
expect(readLoggedCodexEnv(codexEnvLogPath)).toEqual([
|
||||
{
|
||||
CODEX_HOME: codexHome,
|
||||
CODEX_CI: undefined,
|
||||
CODEX_MANAGED_BY_BUN: undefined,
|
||||
CODEX_THREAD_ID: undefined,
|
||||
ANTHROPIC_BASE_URL: undefined,
|
||||
CCS_REMOTE_CLIPROXY_TOKEN: 'ccs-internal-managed',
|
||||
CCS_BROWSER_USER_DATA_DIR: undefined,
|
||||
CCS_BROWSER_PROFILE_DIR: undefined,
|
||||
CCS_BROWSER_DEVTOOLS_WS_URL: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps ccsxp native when the CCS default profile is a Claude account', () => {
|
||||
if (process.platform === 'win32') return;
|
||||
|
||||
|
||||
@@ -239,6 +239,18 @@ describe('Dashboard Auth', () => {
|
||||
expect(isDashboardWebSocketUpgradeAllowed(request)).toBe(true);
|
||||
});
|
||||
|
||||
it('blocks 127-prefixed DNS names from loopback websocket origin aliases', () => {
|
||||
process.env.CCS_DASHBOARD_AUTH_ENABLED = 'false';
|
||||
const request = makeUpgradeRequest('127.0.0.1', false, {
|
||||
host: 'localhost:3001',
|
||||
origin: 'http://127.evil.example.test:3001',
|
||||
});
|
||||
|
||||
expect(isDashboardWebSocketOriginAllowed(request)).toBe(false);
|
||||
expect(isDashboardWebSocketUpgradeAllowed(request)).toBe(false);
|
||||
expect(getDashboardWebSocketRejectionStatus(request)).toBe(403);
|
||||
});
|
||||
|
||||
it('blocks cross-site websocket origins even with an authenticated session', () => {
|
||||
process.env.CCS_DASHBOARD_AUTH_ENABLED = 'true';
|
||||
const request = makeUpgradeRequest('127.0.0.1', true, {
|
||||
|
||||
@@ -34,9 +34,8 @@ describe('web-server claude-extension-routes', () => {
|
||||
({ default: SharedManager } = await import('../../../src/management/shared-manager'));
|
||||
({ createEmptyUnifiedConfig } = await import('../../../src/config/unified-config-types'));
|
||||
({ saveUnifiedConfig } = await import('../../../src/config/unified-config-loader'));
|
||||
({ default: claudeExtensionRoutes } = await import(
|
||||
'../../../src/web-server/routes/claude-extension-routes'
|
||||
));
|
||||
({ default: claudeExtensionRoutes } =
|
||||
await import('../../../src/web-server/routes/claude-extension-routes'));
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
@@ -68,7 +67,8 @@ describe('web-server claude-extension-routes', () => {
|
||||
if (originalCcsHome !== undefined) process.env.CCS_HOME = originalCcsHome;
|
||||
else delete process.env.CCS_HOME;
|
||||
|
||||
if (originalClaudeConfigDir !== undefined) process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir;
|
||||
if (originalClaudeConfigDir !== undefined)
|
||||
process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir;
|
||||
else delete process.env.CLAUDE_CONFIG_DIR;
|
||||
});
|
||||
|
||||
@@ -178,6 +178,47 @@ describe('web-server claude-extension-routes', () => {
|
||||
expect(payload.sharedSettings.json).toContain('"env"');
|
||||
});
|
||||
|
||||
it('blocks non-local setup requests when dashboard auth is disabled', async () => {
|
||||
const app = express();
|
||||
app.use((_req, _res, next) => {
|
||||
Object.defineProperty(_req.socket, 'remoteAddress', {
|
||||
configurable: true,
|
||||
value: '10.0.0.25',
|
||||
});
|
||||
next();
|
||||
});
|
||||
app.use('/api/claude-extension', claudeExtensionRoutes);
|
||||
|
||||
const remoteServer = await new Promise<Server>((resolve, reject) => {
|
||||
const instance = app.listen(0, '127.0.0.1');
|
||||
const handleError = (error: Error) => reject(error);
|
||||
instance.once('error', handleError);
|
||||
instance.once('listening', () => {
|
||||
instance.off('error', handleError);
|
||||
resolve(instance);
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
const address = remoteServer.address();
|
||||
if (!address || typeof address === 'string') {
|
||||
throw new Error('Unable to resolve remote test server port');
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`http://127.0.0.1:${address.port}/api/claude-extension/setup?profile=glm&host=vscode`
|
||||
);
|
||||
expect(response.status).toBe(403);
|
||||
|
||||
const payload = (await response.json()) as { error: string };
|
||||
expect(payload.error).toBe(
|
||||
'Claude extension setup requires localhost access when dashboard auth is disabled.'
|
||||
);
|
||||
} finally {
|
||||
await new Promise<void>((resolve) => remoteServer.close(() => resolve()));
|
||||
}
|
||||
});
|
||||
|
||||
it('normalizes the effective profile CLAUDE_CONFIG_DIR for extension setup', async () => {
|
||||
const explicitConfigDir = path.join(tempHome, '.claude-profiles', 'glm');
|
||||
const glmSettingsPath = path.join(tempHome, '.ccs', 'glm.settings.json');
|
||||
|
||||
Reference in new issue
Block a user