mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 10:16:49 +00:00
fix(cursor): enforce auth token for anthropic daemon route (#1377)
This commit is contained in:
@@ -483,9 +483,8 @@ export async function reconcileExhaustedRotationAccounts(
|
||||
return [];
|
||||
}
|
||||
|
||||
const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } = await import(
|
||||
'../accounts/account-safety'
|
||||
);
|
||||
const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } =
|
||||
await import('../accounts/account-safety');
|
||||
restoreExpiredQuotaPauses();
|
||||
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
@@ -593,9 +592,8 @@ export async function preflightCheck(provider: CLIProxyProvider): Promise<Prefli
|
||||
return { proceed: true, accountId: defaultAccount?.id || '' };
|
||||
}
|
||||
|
||||
const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } = await import(
|
||||
'../accounts/account-safety'
|
||||
);
|
||||
const { pauseAccountForQuotaCooldown, restoreExpiredQuotaPauses } =
|
||||
await import('../accounts/account-safety');
|
||||
restoreExpiredQuotaPauses();
|
||||
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
|
||||
@@ -55,6 +55,23 @@ interface OpenAIChatRequest {
|
||||
|
||||
const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
function getAnthropicRequestToken(headers: http.IncomingHttpHeaders): string {
|
||||
const xApiKey = headers['x-api-key'];
|
||||
if (typeof xApiKey === 'string' && xApiKey.trim().length > 0) {
|
||||
return xApiKey.trim();
|
||||
}
|
||||
|
||||
const authorization = headers.authorization;
|
||||
if (typeof authorization === 'string') {
|
||||
const match = authorization.match(/^Bearer\s+(.+)$/i);
|
||||
if (match && match[1].trim().length > 0) {
|
||||
return match[1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function writeJson(res: http.ServerResponse, statusCode: number, payload: unknown): void {
|
||||
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(payload));
|
||||
@@ -309,6 +326,22 @@ export function startCursorDaemonServer(options: DaemonRuntimeOptions): http.Ser
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAnthropicRoute) {
|
||||
const expectedToken = (process.env.ANTHROPIC_AUTH_TOKEN || 'cursor-managed').trim();
|
||||
const requestToken = getAnthropicRequestToken(req.headers);
|
||||
if (!expectedToken || requestToken !== expectedToken) {
|
||||
await pipeWebResponseToNode(
|
||||
createAnthropicErrorResponse(
|
||||
401,
|
||||
'authentication_error',
|
||||
'Invalid Anthropic auth token. Set ANTHROPIC_AUTH_TOKEN and send it via x-api-key or Authorization Bearer.'
|
||||
),
|
||||
res
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const daemonCredentials = {
|
||||
accessToken: authStatus.credentials.accessToken,
|
||||
machineId: authStatus.credentials.machineId,
|
||||
|
||||
@@ -112,9 +112,8 @@ export async function runImageAnalysisCheck(results: HealthCheck): Promise<void>
|
||||
* Fix image analysis configuration issues
|
||||
*/
|
||||
export async function fixImageAnalysisConfig(): Promise<boolean> {
|
||||
const { updateConfig, loadOrCreateUnifiedConfig } = await import(
|
||||
'../../config/config-loader-facade'
|
||||
);
|
||||
const { updateConfig, loadOrCreateUnifiedConfig } =
|
||||
await import('../../config/config-loader-facade');
|
||||
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
let fixed = false;
|
||||
|
||||
@@ -31,6 +31,41 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe('cursor daemon lifecycle smoke', () => {
|
||||
it('requires Anthropic caller auth token when credentials are present', async () => {
|
||||
const port = 10000 + Math.floor(Math.random() * 50000);
|
||||
|
||||
saveCredentials({
|
||||
accessToken: 'a'.repeat(60),
|
||||
machineId: '1234567890abcdef1234567890abcdef',
|
||||
authMethod: 'manual',
|
||||
importedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const result = await startDaemon({ port, ghost_mode: true });
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${port}/v1/messages`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'anthropic-version': '2023-06-01',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'claude-sonnet-4.5',
|
||||
max_tokens: 64,
|
||||
messages: [{ role: 'user', content: 'hello' }],
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
const body = (await response.json()) as {
|
||||
type?: string;
|
||||
error?: { type?: string; message?: string };
|
||||
};
|
||||
expect(body.type).toBe('error');
|
||||
expect(body.error?.type).toBe('authentication_error');
|
||||
expect(body.error?.message).toContain('Invalid Anthropic auth token');
|
||||
});
|
||||
it('starts, serves expected routes, and stops cleanly', async () => {
|
||||
const port = 10000 + Math.floor(Math.random() * 50000);
|
||||
const result = await startDaemon({ port, ghost_mode: true });
|
||||
|
||||
Reference in New Issue
Block a user