fix(cursor): enforce auth token for anthropic daemon route (#1377)

This commit is contained in:
Kai (Tam Nhu) Tran
2026-05-23 21:45:05 -04:00
committed by GitHub
parent 781c84b04b
commit 210ab761e2
4 changed files with 74 additions and 9 deletions
+4 -6
View File
@@ -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();
+33
View File
@@ -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 });