feat(cursor): harden live probe and runtime contracts

This commit is contained in:
Tam Nhu Tran committed 2026-04-12 01:51:26 -04:00
1 parent 2eaf760d20
commit e1049e38d4
23 files changed
+2186 -306

No files matched your search

+179 -14
View File
@@ -3,6 +3,7 @@
*/
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import { execFileSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
@@ -15,6 +16,7 @@ import {
checkAuthStatus,
deleteCredentials,
autoDetectTokens,
getTokenStorageCandidates,
} from '../../../src/cursor/cursor-auth';
// Test isolation
@@ -403,26 +405,40 @@ describe('deleteCredentials', () => {
});
describe('autoDetectTokens', () => {
it('should return not found for Windows platform', () => {
// Save original platform
it('should report checked paths when no Windows database exists', () => {
const originalPlatform = process.platform;
const originalUserProfile = process.env.USERPROFILE;
const originalAppData = process.env.APPDATA;
const originalLocalAppData = process.env.LOCALAPPDATA;
const fakeHome = path.join(tempDir, 'win-home');
process.env.USERPROFILE = fakeHome;
delete process.env.APPDATA;
delete process.env.LOCALAPPDATA;
// Mock Windows platform
Object.defineProperty(process, 'platform', {
value: 'win32',
configurable: true,
});
const result = autoDetectTokens();
try {
const result = autoDetectTokens();
expect(result.found).toBe(false);
expect(result.error).toContain('not supported on Windows');
// Restore original platform
Object.defineProperty(process, 'platform', {
value: originalPlatform,
configurable: true,
});
expect(result.found).toBe(false);
expect(result.reason).toBe('db_not_found');
expect(result.checkedPaths?.length).toBeGreaterThan(0);
expect(result.error).toContain('Checked:');
} finally {
Object.defineProperty(process, 'platform', {
value: originalPlatform,
configurable: true,
});
if (originalUserProfile !== undefined) process.env.USERPROFILE = originalUserProfile;
else delete process.env.USERPROFILE;
if (originalAppData !== undefined) process.env.APPDATA = originalAppData;
else delete process.env.APPDATA;
if (originalLocalAppData !== undefined) process.env.LOCALAPPDATA = originalLocalAppData;
else delete process.env.LOCALAPPDATA;
}
});
it('should return not found when database file does not exist', () => {
@@ -438,9 +454,10 @@ describe('autoDetectTokens', () => {
try {
const result = autoDetectTokens();
// Should fail because isolated test home has no Cursor database
expect(result.found).toBe(false);
expect(result.error).toBeDefined();
expect(result.reason).toBe('db_not_found');
expect(result.checkedPaths?.length).toBeGreaterThan(0);
expect(result.error).toContain('Checked:');
} finally {
if (originalHome !== undefined) {
process.env.HOME = originalHome;
@@ -457,4 +474,152 @@ describe('autoDetectTokens', () => {
expect(result).toHaveProperty('found');
expect(typeof result.found).toBe('boolean');
});
it('should report sqlite_unavailable when database exists but sqlite3 is missing', () => {
const originalHome = process.env.HOME;
const originalPath = process.env.PATH;
const originalSqliteBin = process.env.CCS_CURSOR_SQLITE_BIN;
const fakeHome = path.join(tempDir, 'sqlite-missing-home');
process.env.HOME = fakeHome;
process.env.CCS_CURSOR_SQLITE_BIN = 'definitely-missing-sqlite3';
try {
const dbPath = getTokenStorageCandidates()[0];
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
fs.writeFileSync(dbPath, '');
const result = autoDetectTokens();
expect(result.found).toBe(false);
expect(result.reason).toBe('sqlite_unavailable');
expect(result.dbPath).toBe(dbPath);
} finally {
if (originalHome !== undefined) process.env.HOME = originalHome;
else delete process.env.HOME;
if (originalPath !== undefined) process.env.PATH = originalPath;
else delete process.env.PATH;
if (originalSqliteBin !== undefined) process.env.CCS_CURSOR_SQLITE_BIN = originalSqliteBin;
else delete process.env.CCS_CURSOR_SQLITE_BIN;
}
});
it('should use fallback keys and normalize JSON-encoded sqlite values', () => {
if (process.platform === 'win32') return;
try {
execFileSync('sqlite3', ['--version'], { stdio: 'ignore' });
} catch {
return;
}
const originalHome = process.env.HOME;
const fakeHome = path.join(tempDir, 'sqlite-fallback-home');
process.env.HOME = fakeHome;
try {
const dbPath = getTokenStorageCandidates()[0];
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
execFileSync('sqlite3', [
dbPath,
'CREATE TABLE IF NOT EXISTS itemTable (key TEXT PRIMARY KEY, value TEXT);',
]);
execFileSync('sqlite3', [
dbPath,
`INSERT OR REPLACE INTO itemTable (key, value) VALUES ('cursorAuth/token', '"${'a'.repeat(60)}"');`,
]);
execFileSync('sqlite3', [
dbPath,
`INSERT OR REPLACE INTO itemTable (key, value) VALUES ('storage.machineId', '"1234567890abcdef1234567890abcdef"');`,
]);
const result = autoDetectTokens();
expect(result.found).toBe(true);
expect(result.accessToken).toBe('a'.repeat(60));
expect(result.machineId).toBe('1234567890abcdef1234567890abcdef');
} finally {
if (originalHome !== undefined) process.env.HOME = originalHome;
else delete process.env.HOME;
}
});
it('should continue scanning later database candidates after one invalid credential pair', () => {
if (process.platform !== 'darwin') {
return;
}
try {
execFileSync('sqlite3', ['--version'], { stdio: 'ignore' });
} catch {
return;
}
const originalHome = process.env.HOME;
const fakeHome = path.join(tempDir, 'candidate-scan-home');
process.env.HOME = fakeHome;
try {
const [stablePath, insidersPath] = getTokenStorageCandidates();
fs.mkdirSync(path.dirname(stablePath), { recursive: true });
fs.mkdirSync(path.dirname(insidersPath), { recursive: true });
execFileSync('sqlite3', [
stablePath,
'CREATE TABLE IF NOT EXISTS itemTable (key TEXT PRIMARY KEY, value TEXT);',
]);
execFileSync('sqlite3', [
stablePath,
`INSERT OR REPLACE INTO itemTable (key, value) VALUES ('cursorAuth/accessToken', 'short');`,
]);
execFileSync('sqlite3', [
stablePath,
`INSERT OR REPLACE INTO itemTable (key, value) VALUES ('storage.serviceMachineId', 'bad');`,
]);
execFileSync('sqlite3', [
insidersPath,
'CREATE TABLE IF NOT EXISTS itemTable (key TEXT PRIMARY KEY, value TEXT);',
]);
execFileSync('sqlite3', [
insidersPath,
`INSERT OR REPLACE INTO itemTable (key, value) VALUES ('cursorAuth/accessToken', '${'a'.repeat(60)}');`,
]);
execFileSync('sqlite3', [
insidersPath,
`INSERT OR REPLACE INTO itemTable (key, value) VALUES ('storage.serviceMachineId', '1234567890abcdef1234567890abcdef');`,
]);
const result = autoDetectTokens();
expect(result.found).toBe(true);
expect(result.dbPath).toBe(insidersPath);
} finally {
if (originalHome !== undefined) process.env.HOME = originalHome;
else delete process.env.HOME;
}
});
it('should report db_query_failed when the database exists but sqlite cannot query it', () => {
if (process.platform === 'win32') return;
try {
execFileSync('sqlite3', ['--version'], { stdio: 'ignore' });
} catch {
return;
}
const originalHome = process.env.HOME;
const fakeHome = path.join(tempDir, 'query-failed-home');
process.env.HOME = fakeHome;
try {
const dbPath = getTokenStorageCandidates()[0];
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
fs.writeFileSync(dbPath, 'not-a-sqlite-database');
const result = autoDetectTokens();
expect(result.found).toBe(false);
expect(result.reason).toBe('db_query_failed');
} finally {
if (originalHome !== undefined) process.env.HOME = originalHome;
else delete process.env.HOME;
}
});
});
+3
View File
@@ -484,6 +484,9 @@ describe('renderCursorHelp', () => {
expect(exitCode).toBe(0);
expect(logs.some((line) => line.includes('Usage: ccs cursor <subcommand>'))).toBe(true);
expect(logs.some((line) => line.includes('probe Run a live authenticated runtime probe'))).toBe(
true
);
expect(
logs.some((line) => line.includes('ccs cursor [claude args]'))
).toBe(true);
+144 -16
View File
@@ -742,14 +742,27 @@ describe('Message Translation', () => {
describe('Request Encoding', () => {
describe('generateCursorBody', () => {
it('should encode basic text message', () => {
it('should encode a raw top-level request protobuf for basic text messages', () => {
const result = generateCursorBody([{ role: 'user', content: 'Hello' }], 'gpt-4', [], null);
const topLevel = decodeMessage(result);
const requestPayload = topLevel.get(FIELD.Request.REQUEST)?.[0]?.value as Uint8Array;
const chatRequest = decodeMessage(requestPayload);
const encodedMessages = (chatRequest.get(FIELD.Chat.MESSAGES) || []).map((entry) =>
decodeMessage(entry.value as Uint8Array)
);
const decoder = new TextDecoder();
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBeGreaterThan(0);
expect(Array.from(result.slice(0, 5))).not.toEqual([0, 0, 0, 1, 107]);
expect(topLevel.has(FIELD.Request.REQUEST)).toBe(true);
expect(encodedMessages).toHaveLength(1);
expect(decoder.decode(encodedMessages[0].get(FIELD.Message.CONTENT)?.[0]?.value as Uint8Array)).toBe(
'Hello'
);
});
it('should encode message with tools', () => {
it('should encode message with tools into the raw request payload', () => {
const tools = [
{
type: 'function' as const,
@@ -773,9 +786,14 @@ describe('Request Encoding', () => {
tools,
null
);
const topLevel = decodeMessage(result);
const requestPayload = topLevel.get(FIELD.Request.REQUEST)?.[0]?.value as Uint8Array;
const chatRequest = decodeMessage(requestPayload);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBeGreaterThan(0);
expect(topLevel.has(FIELD.Request.REQUEST)).toBe(true);
expect((chatRequest.get(FIELD.Chat.MCP_TOOLS) || []).length).toBe(1);
});
it('should preserve flattened tool_result blocks through protobuf encoding', () => {
@@ -806,10 +824,7 @@ describe('Request Encoding', () => {
);
const body = generateCursorBody(translated.messages, 'gpt-4', [], null);
const frame = parseConnectRPCFrame(Buffer.from(body));
expect(frame).not.toBeNull();
const topLevel = decodeMessage(frame!.payload);
const topLevel = decodeMessage(body);
const requestPayload = topLevel.get(FIELD.Request.REQUEST)?.[0]?.value as Uint8Array;
const chatRequest = decodeMessage(requestPayload);
const encodedMessages = (chatRequest.get(FIELD.Chat.MESSAGES) || []).map((entry) =>
@@ -1177,7 +1192,7 @@ describe('CursorExecutor', () => {
});
describe('decompressPayload error handling', () => {
it('should return empty buffer on decompression failure', () => {
it('returns an explicit executor error on decompression failure', async () => {
// Create invalid gzip data
const invalidGzip = Buffer.from([0x1f, 0x8b, 0x08, 0x00, 0xff, 0xff]);
const frame = new Uint8Array(5 + invalidGzip.length);
@@ -1192,13 +1207,15 @@ describe('CursorExecutor', () => {
messages: [],
});
// Should handle gracefully and return valid response
expect(result.status).toBe(200);
expect(result.status).toBe(502);
const body = JSON.parse(await result.text());
expect(body.error.type).toBe('server_error');
expect(body.error.message).toContain('decompress');
});
});
describe('error handling', () => {
it('should return empty buffer on decompression failure', () => {
it('returns an explicit error for invalid compressed payloads', async () => {
const executor = new CursorExecutor();
// Invalid compressed payload (not actually gzipped)
@@ -1217,13 +1234,63 @@ describe('CursorExecutor', () => {
const buffer = Buffer.from(frame);
// Should not crash - decompression failure returns empty buffer
const result = executor.transformProtobufToJSON(buffer, 'test-model', {
messages: [],
stream: false,
});
expect(result.status).toBe(502);
const body = JSON.parse(await result.text());
expect(body.error.type).toBe('server_error');
expect(body.error.message).toContain('decompress');
});
it('surfaces first-frame protocol errors in SSE mode without pretending success', async () => {
const executor = new CursorExecutor();
const invalidGzipPayload = new Uint8Array([1, 2, 3, 4, 5]);
const frame = buildFrame(invalidGzipPayload, 0x01);
const result = executor.transformProtobufToSSE(frame, 'test-model', {
messages: [],
stream: true,
});
expect(result.status).toBe(502);
const body = JSON.parse(await result.text());
expect(body.error.type).toBe('server_error');
expect(body.error.message).toContain('decompress');
});
it('emits an SSE error event without [DONE] when a later frame fails', async () => {
const executor = new CursorExecutor();
const combined = Buffer.concat([
buildTextFrame('Before failure'),
buildFrame(new Uint8Array([1, 2, 3, 4, 5]), 0x01),
]);
const result = executor.transformProtobufToSSE(combined, 'test-model', {
messages: [],
stream: true,
});
expect(result.status).toBe(200);
const body = await result.text();
expect(body).toContain('Before failure');
expect(body).toContain('event: error');
expect(body).toContain('"type":"server_error"');
expect(body).not.toContain('data: [DONE]');
});
it('returns an explicit error for unknown ConnectRPC frame flags', async () => {
const executor = new CursorExecutor();
const result = executor.transformProtobufToJSON(buildFrame(new Uint8Array([0x01]), 0x04), 'gpt-4', {
messages: [],
});
expect(result.status).toBe(502);
const body = JSON.parse(await result.text());
expect(body.error.type).toBe('server_error');
expect(body.error.message).toContain('0x04');
});
it('should log unknown message roles in debug mode', () => {
@@ -1418,6 +1485,31 @@ describe('StreamingFrameParser', () => {
}
});
it('should surface end-stream JSON errors instead of treating them as gzip', () => {
const parser = new StreamingFrameParser();
const endStreamError = buildFrame(
new TextEncoder().encode(
JSON.stringify({
error: {
code: 'invalid_argument',
message: 'parse binary: illegal tag: field no 0 wire type 0',
},
})
),
0x02
);
const results = parser.push(endStreamError);
expect(results.length).toBe(1);
expect(results[0].type).toBe('error');
if (results[0].type === 'error') {
expect(results[0].status).toBe(400);
expect(results[0].errorType).toBe('api_error');
expect(results[0].message).toContain('illegal tag');
}
});
it('should parse thinking frames', () => {
const parser = new StreamingFrameParser();
const frame = buildThinkingFrame('Think step by step');
@@ -1458,11 +1550,39 @@ describe('StreamingFrameParser', () => {
expect(results.length).toBe(1);
expect(results[0].type).toBe('error');
if (results[0].type === 'error') {
expect(results[0].status).toBe(502);
expect(results[0].errorType).toBe('server_error');
expect(results[0].message).toContain('Malformed protobuf response');
}
});
it('should reject invalid gzip-compressed frames explicitly', () => {
const parser = new StreamingFrameParser();
const invalidCompressedFrame = buildFrame(new Uint8Array([1, 2, 3, 4, 5]), 0x01);
const results = parser.push(invalidCompressedFrame);
expect(results.length).toBe(1);
expect(results[0].type).toBe('error');
if (results[0].type === 'error') {
expect(results[0].status).toBe(502);
expect(results[0].errorType).toBe('server_error');
expect(results[0].message).toContain('decompress');
}
});
it('should reject unknown ConnectRPC frame flag bits', () => {
const parser = new StreamingFrameParser();
const results = parser.push(buildFrame(new Uint8Array([0x01]), 0x04));
expect(results.length).toBe(1);
expect(results[0].type).toBe('error');
if (results[0].type === 'error') {
expect(results[0].status).toBe(502);
expect(results[0].errorType).toBe('server_error');
expect(results[0].message).toContain('0x04');
}
});
it('should report hasPartial() correctly', () => {
const parser = new StreamingFrameParser();
expect(parser.hasPartial()).toBe(false);
@@ -1496,10 +1616,18 @@ describe('decompressPayload', () => {
expect(result).toEqual(errorPayload);
});
it('should return empty buffer on invalid gzip data', () => {
it('should throw on invalid gzip data', () => {
const invalidGzip = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]);
const result = decompressPayload(invalidGzip, 0x01);
expect(result.length).toBe(0);
expect(() => decompressPayload(invalidGzip, 0x01)).toThrow(
'Failed to decompress Cursor ConnectRPC frame.'
);
});
it('should throw on unknown ConnectRPC frame flags', () => {
const payload = Buffer.from('payload');
expect(() => decompressPayload(payload, 0x04)).toThrow(
'Unsupported ConnectRPC frame flags: 0x04'
);
});
it('should decompress valid gzip payload', () => {
@@ -1511,12 +1639,12 @@ describe('decompressPayload', () => {
expect(result.toString()).toBe('Hello compressed world');
});
it('should handle GZIP_ALT and GZIP_BOTH flags', () => {
it('should not decompress plain end-stream trailers and should handle compressed end-stream trailers', () => {
const zlib = require('zlib');
const original = Buffer.from('test data');
const compressed = zlib.gzipSync(original);
expect(decompressPayload(compressed, 0x02).toString()).toBe('test data');
expect(decompressPayload(original, 0x02)).toEqual(original);
expect(decompressPayload(compressed, 0x03).toString()).toBe('test data');
});
});
@@ -0,0 +1,132 @@
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import * as fs from 'fs';
import * as http from 'http';
import * as os from 'os';
import * as path from 'path';
import type { CursorConfig } from '../../../src/config/unified-config-types';
let tempDir = '';
let originalCcsHome: string | undefined;
let originalFetch: typeof globalThis.fetch;
let setGlobalConfigDir: (dir: string | undefined) => void;
let saveCredentials: (credentials: {
accessToken: string;
machineId: string;
authMethod: 'manual' | 'auto-detect';
importedAt: string;
}) => void;
let deleteCredentials: () => boolean;
let probeCursorRuntime: (config: CursorConfig) => Promise<{
ok: boolean;
stage: 'config' | 'auth' | 'daemon' | 'runtime';
status: number;
duration_ms: number;
error_type?: string | null;
message: string;
}>;
beforeEach(async () => {
originalCcsHome = process.env.CCS_HOME;
originalFetch = globalThis.fetch;
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-cursor-runtime-probe-test-'));
process.env.CCS_HOME = tempDir;
const configManager = await import('../../../src/utils/config-manager');
setGlobalConfigDir = configManager.setGlobalConfigDir;
setGlobalConfigDir(undefined);
const cursorAuth = await import('../../../src/cursor/cursor-auth');
saveCredentials = cursorAuth.saveCredentials;
deleteCredentials = cursorAuth.deleteCredentials;
const runtimeProbe = await import(
`../../../src/cursor/cursor-runtime-probe?cursor-runtime-probe-test=${Date.now()}`
);
probeCursorRuntime = runtimeProbe.probeCursorRuntime;
deleteCredentials();
});
afterEach(() => {
globalThis.fetch = originalFetch;
if (originalCcsHome !== undefined) {
process.env.CCS_HOME = originalCcsHome;
} else {
delete process.env.CCS_HOME;
}
setGlobalConfigDir(undefined);
if (tempDir && fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
describe('probeCursorRuntime', () => {
it('classifies daemon connection races as daemon-stage failures', async () => {
const port = 23000 + Math.floor(Math.random() * 2000);
saveCredentials({
accessToken: 'a'.repeat(60),
machineId: '1234567890abcdef1234567890abcdef',
authMethod: 'manual',
importedAt: new Date().toISOString(),
});
const server = http.createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ service: 'cursor-daemon' }));
setImmediate(() => {
server.close();
});
return;
}
res.writeHead(404);
res.end();
});
await new Promise<void>((resolve) => server.listen(port, '127.0.0.1', () => resolve()));
globalThis.fetch = (async (input: RequestInfo | URL) => {
const url = String(input);
if (url.startsWith('https://api2.cursor.sh')) {
return new Response(
JSON.stringify({
models: [{ name: 'gpt-5.3-codex' }],
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' },
}
);
}
if (url.startsWith(`http://127.0.0.1:${port}/v1/chat/completions`)) {
const error = new Error('fetch failed') as Error & { cause?: { code: string } };
error.cause = { code: 'ECONNREFUSED' };
throw error;
}
throw new Error(`Unexpected fetch: ${url}`);
}) as typeof globalThis.fetch;
const result = await probeCursorRuntime({
enabled: true,
port,
auto_start: false,
ghost_mode: true,
model: 'gpt-5.3-codex',
} as CursorConfig);
expect(result.ok).toBe(false);
expect(result.stage).toBe('daemon');
expect(result.status).toBe(503);
expect(result.error_type).toBe('daemon_unreachable');
expect(result.message).toContain('unreachable');
});
});