diff --git a/src/cliproxy/quota/quota-manager.ts b/src/cliproxy/quota/quota-manager.ts index 8574b079..041e2748 100644 --- a/src/cliproxy/quota/quota-manager.ts +++ b/src/cliproxy/quota/quota-manager.ts @@ -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 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, diff --git a/src/management/checks/image-analysis-check.ts b/src/management/checks/image-analysis-check.ts index 50e9ba99..d6d2dc05 100644 --- a/src/management/checks/image-analysis-check.ts +++ b/src/management/checks/image-analysis-check.ts @@ -112,9 +112,8 @@ export async function runImageAnalysisCheck(results: HealthCheck): Promise * Fix image analysis configuration issues */ export async function fixImageAnalysisConfig(): Promise { - const { updateConfig, loadOrCreateUnifiedConfig } = await import( - '../../config/config-loader-facade' - ); + const { updateConfig, loadOrCreateUnifiedConfig } = + await import('../../config/config-loader-facade'); const config = loadOrCreateUnifiedConfig(); let fixed = false; diff --git a/tests/integration/cursor-daemon-lifecycle.test.ts b/tests/integration/cursor-daemon-lifecycle.test.ts index 09acca70..c50aea68 100644 --- a/tests/integration/cursor-daemon-lifecycle.test.ts +++ b/tests/integration/cursor-daemon-lifecycle.test.ts @@ -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 });