fix(cursor): harden auth security and add token expiry warning

This commit is contained in:
Tam Nhu Tran
2026-02-11 19:06:57 +07:00
parent 56270d99f3
commit aeb580281f
2 changed files with 28 additions and 8 deletions
+26 -8
View File
@@ -14,7 +14,7 @@
* - storage.serviceMachineId: Machine ID for checksum * - storage.serviceMachineId: Machine ID for checksum
*/ */
import { execSync } from 'child_process'; import { execFileSync } from 'child_process';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
@@ -52,9 +52,10 @@ export function getTokenStoragePath(): string {
*/ */
function queryStateDb(dbPath: string, key: string): string | null { function queryStateDb(dbPath: string, key: string): string | null {
try { try {
const result = execSync( const result = execFileSync(
`sqlite3 "${dbPath}" "SELECT value FROM itemTable WHERE key='${key}'" 2>/dev/null`, 'sqlite3',
{ encoding: 'utf8', timeout: 5000 } [dbPath, `SELECT value FROM itemTable WHERE key='${key}'`],
{ encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'ignore'] }
).trim(); ).trim();
return result || null; return result || null;
} catch { } catch {
@@ -66,6 +67,15 @@ function queryStateDb(dbPath: string, key: string): string | null {
* Auto-detect tokens from Cursor's SQLite database * Auto-detect tokens from Cursor's SQLite database
*/ */
export function autoDetectTokens(): AutoDetectResult { export function autoDetectTokens(): AutoDetectResult {
// sqlite3 CLI is not bundled with Windows
if (process.platform === 'win32') {
return {
found: false,
error:
'Auto-detection is not supported on Windows. Please import tokens manually using ccs cursor auth --manual.',
};
}
const dbPath = getTokenStoragePath(); const dbPath = getTokenStoragePath();
// Check if database exists // Check if database exists
@@ -172,13 +182,16 @@ export function saveCredentials(credentials: CursorCredentials): void {
const credPath = getCredentialsPath(); const credPath = getCredentialsPath();
const dir = path.dirname(credPath); const dir = path.dirname(credPath);
// Ensure directory exists // Ensure directory exists with restrictive permissions
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
} }
// Write credentials // Write credentials with restrictive permissions
fs.writeFileSync(credPath, JSON.stringify(credentials, null, 2), 'utf8'); fs.writeFileSync(credPath, JSON.stringify(credentials, null, 2), {
encoding: 'utf8',
mode: 0o600,
});
} }
/** /**
@@ -230,10 +243,14 @@ export function checkAuthStatus(): CursorAuthStatus {
// Calculate token age in hours // Calculate token age in hours
let tokenAge: number | undefined; let tokenAge: number | undefined;
let expired = false;
const TOKEN_EXPIRY_HOURS = 24;
try { try {
const importedDate = new Date(credentials.importedAt); const importedDate = new Date(credentials.importedAt);
const now = new Date(); const now = new Date();
tokenAge = Math.floor((now.getTime() - importedDate.getTime()) / (1000 * 60 * 60)); tokenAge = Math.floor((now.getTime() - importedDate.getTime()) / (1000 * 60 * 60));
expired = tokenAge >= TOKEN_EXPIRY_HOURS;
} catch { } catch {
// Invalid date format // Invalid date format
} }
@@ -242,5 +259,6 @@ export function checkAuthStatus(): CursorAuthStatus {
authenticated: true, authenticated: true,
credentials, credentials,
tokenAge, tokenAge,
expired,
}; };
} }
+2
View File
@@ -32,6 +32,8 @@ export interface CursorAuthStatus {
credentials?: CursorCredentials; credentials?: CursorCredentials;
/** Hours since credentials were imported (if available) */ /** Hours since credentials were imported (if available) */
tokenAge?: number; tokenAge?: number;
/** Whether token has expired (>24 hours old) */
expired?: boolean;
} }
/** /**