ЧАСТЬ V. БЕЗОПАСНОСТЬ И КОНФИДЕНЦИАЛЬНОСТЬ
Глава 17. Защита данных при работе с Cursor
17.1. Политика конфиденциальности Cursor
Понимание обработки данных
Cursor серьезно относится к вопросам конфиденциальности и безопасности пользовательских данных. Важно понимать, какие данные обрабатываются и как они защищены.
Типы обрабатываемых данных:
-
Код и контекст
- Фрагменты кода, отправляемые в AI для анализа
- Структура проекта (без всего содержимого)
- История изменений в текущей сессии
-
Метаданные
- Используемые языки программирования
- Частота использования функций
- Производительность системы
-
Учетные данные
- Email для аутентификации
- Информация о подписке
- Настройки пользователя
Что НЕ отправляется:
- Полная кодовая база (только релевантные фрагменты)
- Секреты, токены, пароли (автоматическое обнаружение и фильтрация)
- Файлы из
.gitignoreи.cursorignore - Бинарные файлы
17.2. Обработка чувствительных данных
You: Как безопасно работать с конфиденциальным кодом в Cursor?
AI:Best Practices для защиты данных:
# .cursorignore
# Исключить конфиденциальные файлы из индексации Cursor
# Секреты и учетные данные
.env
.env.*
*.pem
*.key
*.p12
*.pfx
secrets/
credentials/
# Конфигурационные файлы с секретами
config/database.yml
config/secrets.yml
aws-credentials.json
# Логи с потенциально чувствительной информацией
*.log
logs/
# Бэкапы и дампы
*.sql
*.dump
backups/
# Проприетарный код (если применимо)
proprietary/
internal/
# Зависимости
node_modules/
vendor/
venv/
Автоматическая защита секретов:
# security/secret_scanner.py
import re
from typing import List, Dict
class SecretScanner:
"""
Сканер для обнаружения потенциальных секретов в коде
"""
PATTERNS = {
'api_key': r'(?i)(api[_-]?key|apikey)[\s]*[:=][\s]*[\'"]?([a-zA-Z0-9_\-]+)[\'"]?',
'aws_key': r'AKIA[0-9A-Z]{16}',
'private_key': r'-----BEGIN (RSA |EC )?PRIVATE KEY-----',
'password': r'(?i)(password|passwd|pwd)[\s]*[:=][\s]*[\'"]([^\'"]+)[\'"]',
'token': r'(?i)(token|auth)[\s]*[:=][\s]*[\'"]?([a-zA-Z0-9_\-\.]+)[\'"]?',
'connection_string': r'(mongodb(\+srv)?|postgres|mysql):\/\/[^\s]+',
}
def scan_file(self, file_path: str) -> List[Dict]:
"""Сканировать файл на наличие секретов"""
findings = []
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
for secret_type, pattern in self.PATTERNS.items():
matches = re.finditer(pattern, content)
for match in matches:
findings.append({
'type': secret_type,
'file': file_path,
'line': content[:match.start()].count('\n') + 1,
'match': match.group(0)[:50] # First 50 chars
})
except Exception as e:
print(f"Error scanning {file_path}: {e}")
return findings
def scan_directory(self, directory: str) -> List[Dict]:
"""Сканировать директорию"""
import os
findings = []
for root, dirs, files in os.walk(directory):
# Skip excluded directories
dirs[:] = [d for d in dirs if d not in ['.git', 'node_modules', 'venv']]
for file in files:
if file.endswith(('.py', '.js', '.ts', '.java', '.go', '.rb')):
file_path = os.path.join(root, file)
findings.extend(self.scan_file(file_path))
return findings
# Использование
scanner = SecretScanner()
findings = scanner.scan_directory('.')
if findings:
print("Potential secrets found:")
for finding in findings:
print(f" {finding['type']} in {finding['file']}:{finding['line']}")
else:
print(" No secrets detected")
17.3. Privacy Mode
Включение режима конфиденциальности:
Privacy Mode в Cursor позволяет работать с кодом без отправки его в облако.
// .cursor/settings.json
{
"cursor.privacyMode": true,
"cursor.localModel": true,
"cursor.telemetry": false,
"cursor.analytics": false
}
Возможности в Privacy Mode:
- Локальное автодополнение (базовое)
- Синтаксическая подсветка
- Рефакторинг на основе статического анализа
- AI Chat (требует облачных моделей)
- Сложная генерация кода
Гибридный подход:
# utils/cursor_config.py
"""
Утилита для переключения между режимами
в зависимости от типа проекта
"""
import json
import os
class CursorConfig:
def __init__(self, project_root):
self.project_root = project_root
self.config_path = os.path.join(project_root, '.cursor', 'settings.json')
def enable_privacy_mode(self):
"""Включить режим конфиденциальности"""
config = self._load_config()
config.update({
'cursor.privacyMode': True,
'cursor.telemetry': False
})
self._save_config(config)
print("✓ Privacy mode enabled")
def disable_privacy_mode(self):
"""Выключить режим конфиденциальности"""
config = self._load_config()
config.update({
'cursor.privacyMode': False,
'cursor.telemetry': True
})
self._save_config(config)
print("✓ Privacy mode disabled")
def _load_config(self):
if os.path.exists(self.config_path):
with open(self.config_path, 'r') as f:
return json.load(f)
return {}
def _save_config(self, config):
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
with open(self.config_path, 'w') as f:
json.dump(config, f, indent=2)
# Использование
config = CursorConfig('.')
# Для работы с конфиденциальным кодом
if is_confidential_project():
config.enable_privacy_mode()
else:
config.disable_privacy_mode()
17.4. Локальное хранение
Конфигурация локального хранилища
You: Настрой Cursor для работы без отправки кода в облако
AI:// .cursor/local-config.json
{
"storage": {
"mode": "local",
"path": "${HOME}/.cursor/local-storage",
"encryption": true,
"encryptionKey": "${CURSOR_ENCRYPTION_KEY}"
},
"models": {
"useLocalModels": true,
"modelPath": "${HOME}/.cursor/models",
"fallbackToCloud": false
},
"indexing": {
"storeLocally": true,
"indexPath": "${HOME}/.cursor/indexes/${PROJECT_HASH}",
"encryptIndex": true
},
"telemetry": {
"enabled": false,
"crashReports": false,
"usageStatistics": false
}
}
Скрипт для настройки локального окружения:
#!/bin/bash
# setup-local-cursor.sh
set -e
echo "Setting up Cursor for local-only operation..."
# Create directories
CURSOR_HOME="$HOME/.cursor"
mkdir -p "$CURSOR_HOME/local-storage"
mkdir -p "$CURSOR_HOME/models"
mkdir -p "$CURSOR_HOME/indexes"
# Generate encryption key
ENCRYPTION_KEY=$(openssl rand -base64 32)
echo "CURSOR_ENCRYPTION_KEY=$ENCRYPTION_KEY" > "$CURSOR_HOME/.env"
echo "Directories created"
echo "Encryption key generated"
# Set permissions
chmod 700 "$CURSOR_HOME"
chmod 600 "$CURSOR_HOME/.env"
echo "Permissions set"
# Copy local config
cat > "$CURSOR_HOME/local-config.json" <<EOF
{
"storage": {
"mode": "local",
"path": "$CURSOR_HOME/local-storage",
"encryption": true
},
"models": {
"useLocalModels": true,
"modelPath": "$CURSOR_HOME/models"
},
"indexing": {
"storeLocally": true,
"indexPath": "$CURSOR_HOME/indexes"
},
"telemetry": {
"enabled": false
}
}
EOF
echo "✓ Configuration created"
# Download local models (if available)
# This is a placeholder - actual implementation depends on Cursor's API
# download_local_models() {
# echo "Downloading local models..."
# # Model download logic
# }
echo ""
echo "================================"
echo "Local setup completed!"
echo "================================"
echo "Encryption key stored in: $CURSOR_HOME/.env"
echo "Make sure to backup this key securely!"
echo ""
echo "To use local mode, set in Cursor settings:"
echo " Cursor > Privacy Mode: Enabled"
echo " Cursor > Use Local Models: Enabled"
17.5. Соответствие стандартам (GDPR, SOC 2)
GDPR Compliance проверка
You: Создай checklist для GDPR compliance в приложении
AI:# compliance/gdpr-checklist.yml
---
gdpr_compliance:
lawful_basis:
- item: "Определена законная основа обработки"
status: "required"
implementation:
- "Consent для маркетинговых коммуникаций"
- "Contract для обработки заказов"
- "Legitimate interest для аналитики"
data_minimization:
- item: "Собираются только необходимые данные"
status: "required"
checks:
- "Форма регистрации: email, имя (optional адрес только при заказе)"
- "Не собираем: дату рождения, пол без необходимости"
- item: "Данные удаляются после истечения срока хранения"
status: "required"
implementation: |
- Inactive accounts: 2 года
- Logs: 90 дней
- Analytics: 26 месяцев (Google Analytics стандарт)
user_rights:
- right: "Right to access"
endpoint: "GET /api/users/me/data-export"
implementation: |
Пользователь может запросить полный экспорт своих данных
в машиночитаемом формате (JSON)
- right: "Right to rectification"
endpoint: "PUT /api/users/me"
implementation: "Пользователь может обновить свои данные"
- right: "Right to erasure"
endpoint: "DELETE /api/users/me"
implementation: |
Полное удаление учетной записи и всех связанных данных
с 30-дневным grace period для восстановления
- right: "Right to data portability"
endpoint: "GET /api/users/me/data-export?format=json"
formats: ["JSON", "CSV"]
- right: "Right to object"
endpoint: "POST /api/users/me/consent"
implementation: "Управление согласиями на обработку"
security_measures:
- measure: "Encryption in transit"
implementation:
- "TLS 1.3 для всех соединений"
- "HSTS headers"
- "Certificate pinning в мобильных приложениях"
- measure: "Encryption at rest"
implementation:
- "Database: encryption at rest (AWS RDS)"
- "Backups: encrypted S3 buckets"
- "Passwords: bcrypt with cost factor 12"
- measure: "Access controls"
implementation:
- "Role-based access control (RBAC)"
- "Multi-factor authentication для админов"
- "IP whitelisting для admin panel"
- measure: "Audit logging"
implementation:
- "Все доступы к персональным данным логируются"
- "Логи хранятся в защищенном S3 bucket"
- "Retention: 1 год"
data_processing:
- processor: "AWS (Infrastructure)"
dpa_signed: true
location: "EU (eu-west-1)"
- processor: "Anthropic (AI Processing)"
dpa_signed: true
purpose: "Code analysis and generation"
data_sent: "Code snippets only (no full codebase)"
- processor: "Sentry (Error Monitoring)"
dpa_signed: true
data_sent: "Error logs (sanitized)"
breach_response:
- step: "Detection"
sla: "< 24 hours"
- step: "Assessment"
sla: "< 48 hours"
actions:
- "Определить scope breach"
- "Оценить риски для пользователей"
- step: "Notification"
sla: "< 72 hours"
recipients:
- "Затронутые пользователи"
- "Регулятор (если > 500 пользователей)"
- step: "Remediation"
actions:
- "Устранить уязвимость"
- "Провести security audit"
- "Обновить процедуры"
Реализация GDPR endpoints:
// routes/gdpr.routes.ts
import { Router } from 'express';
import { authenticate } from '../middleware/auth';
import { GDPRController } from '../controllers/gdpr.controller';
import { rateLimit } from 'express-rate-limit';
const router = Router();
const gdprController = new GDPRController();
// Rate limiting для GDPR endpoints
const gdprLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 requests per hour
message: 'Too many GDPR requests, please try again later'
});
/**
* @route GET /api/gdpr/export
* @desc Export all user data (Right to access)
* @access Private
*/
router.get(
'/export',
authenticate,
gdprLimiter,
gdprController.exportUserData
);
/**
* @route DELETE /api/gdpr/delete-account
* @desc Request account deletion (Right to erasure)
* @access Private
*/
router.delete(
'/delete-account',
authenticate,
gdprController.requestAccountDeletion
);
/**
* @route POST /api/gdpr/consent
* @desc Manage consents (Right to object)
* @access Private
*/
router.post(
'/consent',
authenticate,
gdprController.updateConsent
);
/**
* @route GET /api/gdpr/consent
* @desc Get current consents
* @access Private
*/
router.get(
'/consent',
authenticate,
gdprController.getConsents
);
export default router;
// controllers/gdpr.controller.ts
import { Request, Response, NextFunction } from 'express';
import { GDPRService } from '../services/gdpr.service';
import { logger } from '../utils/logger';
export class GDPRController {
private gdprService: GDPRService;
constructor() {
this.gdprService = new GDPRService();
}
async exportUserData(req: Request, res: Response, next: NextFunction) {
try {
const userId = (req as any).user.id;
logger.info(`Data export requested by user: ${userId}`);
// Generate comprehensive data export
const userData = await this.gdprService.exportAllUserData(userId);
// Log the export for audit
await this.gdprService.logDataExport(userId);
// Return as downloadable JSON
res.setHeader('Content-Disposition', `attachment; filename="user-data-${userId}.json"`);
res.setHeader('Content-Type', 'application/json');
res.json({
exportDate: new Date().toISOString(),
userId: userId,
data: userData,
dataProcessors: [
{
name: 'AWS',
purpose: 'Infrastructure hosting',
location: 'EU-WEST-1'
},
{
name: 'Anthropic',
purpose: 'AI code analysis',
dataShared: 'Code snippets only'
}
]
});
} catch (error) {
logger.error('Error exporting user data:', error);
next(error);
}
}
async requestAccountDeletion(req: Request, res: Response, next: NextFunction) {
try {
const userId = (req as any).user.id;
const { reason } = req.body;
logger.info(`Account deletion requested by user: ${userId}`);
// Create deletion request with 30-day grace period
const deletionRequest = await this.gdprService.createDeletionRequest(
userId,
reason
);
// Send confirmation email
await this.gdprService.sendDeletionConfirmationEmail(userId);
res.json({
success: true,
message: 'Account deletion scheduled',
deletionDate: deletionRequest.scheduledDate,
gracePeriod: '30 days',
cancellationDeadline: deletionRequest.cancellationDeadline
});
} catch (error) {
logger.error('Error processing deletion request:', error);
next(error);
}
}
async updateConsent(req: Request, res: Response, next: NextFunction) {
try {
const userId = (req as any).user.id;
const consents = req.body;
// Validate consents
const validConsents = ['marketing', 'analytics', 'personalization'];
for (const key of Object.keys(consents)) {
if (!validConsents.includes(key)) {
return res.status(400).json({
success: false,
error: `Invalid consent type: ${key}`
});
}
}
// Update consents
await this.gdprService.updateUserConsents(userId, consents);
logger.info(`Consents updated for user: ${userId}`, { consents });
res.json({
success: true,
message: 'Consents updated successfully',
consents: consents
});
} catch (error) {
logger.error('Error updating consents:', error);
next(error);
}
}
async getConsents(req: Request, res: Response, next: NextFunction) {
try {
const userId = (req as any).user.id;
const consents = await this.gdprService.getUserConsents(userId);
res.json({
success: true,
data: consents
});
} catch (error) {
next(error);
}
}
}
// services/gdpr.service.ts
import { db } from '../database';
import { EmailService } from './email.service';
import { AuditLogger } from '../utils/audit-logger';
export class GDPRService {
private emailService: EmailService;
private auditLogger: AuditLogger;
constructor() {
this.emailService = new EmailService();
this.auditLogger = new AuditLogger();
}
async exportAllUserData(userId: string) {
/**
* Export comprehensive user data in machine-readable format
*/
// Collect all user-related data
const [
profile,
orders,
addresses,
paymentMethods,
reviews,
wishlist,
activityLog,
consents
] = await Promise.all([
db.users.findById(userId),
db.orders.findByUser(userId),
db.addresses.findByUser(userId),
db.paymentMethods.findByUser(userId),
db.reviews.findByUser(userId),
db.wishlist.findByUser(userId),
db.activityLog.findByUser(userId, { limit: 1000 }),
db.consents.findByUser(userId)
]);
// Sanitize sensitive data
const sanitizedPaymentMethods = paymentMethods.map(pm => ({
...pm,
cardNumber: `****-****-****-${pm.lastFourDigits}`,
cvv: undefined
}));
return {
personalInformation: {
id: profile.id,
email: profile.email,
name: profile.name,
phone: profile.phone,
createdAt: profile.createdAt,
lastLogin: profile.lastLogin
},
orders: orders.map(order => ({
id: order.id,
date: order.createdAt,
total: order.total,
status: order.status,
items: order.items
})),
addresses: addresses,
paymentMethods: sanitizedPaymentMethods,
reviews: reviews,
wishlist: wishlist,
activityLog: activityLog.map(log => ({
action: log.action,
timestamp: log.timestamp,
ipAddress: this.anonymizeIP(log.ipAddress)
})),
consents: consents
};
}
async createDeletionRequest(userId: string, reason?: string) {
const deletionDate = new Date();
deletionDate.setDate(deletionDate.getDate() + 30); // 30-day grace period
const request = await db.deletionRequests.create({
userId,
reason,
requestDate: new Date(),
scheduledDate: deletionDate,
cancellationDeadline: deletionDate,
status: 'pending'
});
// Audit log
await this.auditLogger.log({
action: 'DELETION_REQUEST_CREATED',
userId,
metadata: { requestId: request.id, reason }
});
return request;
}
async executeAccountDeletion(userId: string) {
/**
* Permanently delete user account and all associated data
* This is irreversible
*/
// Start transaction
const session = await db.startSession();
session.startTransaction();
try {
// Log all data before deletion for audit
await this.auditLogger.log({
action: 'ACCOUNT_DELETION_STARTED',
userId,
metadata: {
dataExport: await this.exportAllUserData(userId)
}
});
// Delete from all tables
await Promise.all([
db.orders.deleteByUser(userId, { session }),
db.reviews.deleteByUser(userId, { session }),
db.addresses.deleteByUser(userId, { session }),
db.paymentMethods.deleteByUser(userId, { session }),
db.wishlist.deleteByUser(userId, { session }),
db.activityLog.deleteByUser(userId, { session }),
db.consents.deleteByUser(userId, { session })
]);
// Anonymize data that must be retained for legal reasons
await db.orders.anonymizeUser(userId, { session });
// Finally delete user account
await db.users.delete(userId, { session });
await session.commitTransaction();
// Final audit log
await this.auditLogger.log({
action: 'ACCOUNT_DELETION_COMPLETED',
userId,
metadata: { timestamp: new Date() }
});
logger.info(`Account deleted: ${userId}`);
} catch (error) {
await session.abortTransaction();
logger.error('Error deleting account:', error);
throw error;
} finally {
session.endSession();
}
}
async updateUserConsents(
userId: string,
consents: Record<string, boolean>
) {
const timestamp = new Date();
for (const [consentType, granted] of Object.entries(consents)) {
await db.consents.upsert({
userId,
consentType,
granted,
timestamp
});
}
// If user withdraws marketing consent, unsubscribe
if (consents.marketing === false) {
await this.emailService.unsubscribeUser(userId);
}
// Audit log
await this.auditLogger.log({
action: 'CONSENTS_UPDATED',
userId,
metadata: { consents, timestamp }
});
}
async getUserConsents(userId: string) {
return await db.consents.findByUser(userId);
}
async logDataExport(userId: string) {
await this.auditLogger.log({
action: 'DATA_EXPORTED',
userId,
metadata: {
timestamp: new Date(),
ipAddress: 'tracked separately'
}
});
}
async sendDeletionConfirmationEmail(userId: string) {
const user = await db.users.findById(userId);
await this.emailService.send({
to: user.email,
subject: 'Account Deletion Confirmation',
template: 'account-deletion',
data: {
name: user.name,
deletionDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
cancellationLink: `${process.env.APP_URL}/account/cancel-deletion`
}
});
}
private anonymizeIP(ip: string): string {
// Anonymize last octet for IPv4
const parts = ip.split('.');
if (parts.length === 4) {
parts[3] = '0';
return parts.join('.');
}
return ip;
}
}