fix: improve Cursor CLI implementation with type safety and security fixes

- Add getCliPath() public method to CursorProvider to avoid private field access
- Add path validation to cursor-config routes to prevent traversal attacks
- Add supportsVision field to CursorModelConfig (all false - CLI limitation)
- Consolidate duplicate types in providers/types.ts (re-export from @automaker/types)
- Add MCP servers warning log instead of error (not yet supported by Cursor CLI)
- Fix debug log type safety (replace 'as any' with proper type narrowing)
- Update docs to remove non-existent tier field, add supportsVision field
- Remove outdated TODO comment in sdk-options.ts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Shirone
2026-01-03 03:35:33 +01:00
parent ec6d36bda5
commit 88aba360e3
7 changed files with 104 additions and 93 deletions

View File

@@ -14,6 +14,7 @@
*/
import type { Request, Response } from 'express';
import path from 'path';
import { CursorConfigManager } from '../../../providers/cursor-config-manager.js';
import {
CURSOR_MODEL_MAP,
@@ -37,6 +38,27 @@ import {
} from '../../../services/cursor-config-service.js';
import { getErrorMessage, logError } from '../common.js';
/**
* Validate that a project path is safe (no path traversal)
* @throws Error if path contains traversal sequences
*/
function validateProjectPath(projectPath: string): void {
// Resolve to absolute path and check for traversal
const resolved = path.resolve(projectPath);
const normalized = path.normalize(projectPath);
// Check for obvious traversal attempts
if (normalized.includes('..') || projectPath.includes('..')) {
throw new Error('Invalid project path: path traversal not allowed');
}
// Ensure the resolved path doesn't escape intended boundaries
// by checking if it starts with the normalized path components
if (!resolved.startsWith(path.resolve(normalized))) {
throw new Error('Invalid project path: path traversal detected');
}
}
/**
* Creates handler for GET /api/setup/cursor-config
* Returns current Cursor configuration and available models
@@ -54,6 +76,9 @@ export function createGetCursorConfigHandler() {
return;
}
// Validate path to prevent traversal attacks
validateProjectPath(projectPath);
const configManager = new CursorConfigManager(projectPath);
res.json({
@@ -88,6 +113,9 @@ export function createSetCursorDefaultModelHandler() {
return;
}
// Validate path to prevent traversal attacks
validateProjectPath(projectPath);
if (!model || !(model in CURSOR_MODEL_MAP)) {
res.status(400).json({
success: false,
@@ -127,6 +155,9 @@ export function createSetCursorModelsHandler() {
return;
}
// Validate path to prevent traversal attacks
validateProjectPath(projectPath);
if (!Array.isArray(models)) {
res.status(400).json({
success: false,
@@ -173,6 +204,11 @@ export function createGetCursorPermissionsHandler() {
try {
const projectPath = req.query.projectPath as string | undefined;
// Validate path if provided
if (projectPath) {
validateProjectPath(projectPath);
}
// Get global config
const globalConfig = await readGlobalConfig();
@@ -238,6 +274,8 @@ export function createApplyPermissionProfileHandler() {
});
return;
}
// Validate path to prevent traversal attacks
validateProjectPath(projectPath);
await applyProfileToProject(projectPath, profileId);
} else {
await applyProfileGlobally(profileId);
@@ -279,6 +317,9 @@ export function createSetCustomPermissionsHandler() {
return;
}
// Validate path to prevent traversal attacks
validateProjectPath(projectPath);
if (!permissions || !Array.isArray(permissions.allow) || !Array.isArray(permissions.deny)) {
res.status(400).json({
success: false,
@@ -324,6 +365,9 @@ export function createDeleteProjectPermissionsHandler() {
return;
}
// Validate path to prevent traversal attacks
validateProjectPath(projectPath);
await deleteProjectConfig(projectPath);
res.json({

View File

@@ -24,10 +24,8 @@ export function createCursorStatusHandler() {
provider.checkAuth(),
]);
// Get CLI path from provider (using type assertion since cliPath is private)
const cliPath = installed
? (provider as unknown as { cliPath: string | null }).cliPath
: null;
// Get CLI path from provider using public accessor
const cliPath = installed ? provider.getCliPath() : null;
res.json({
success: true,