---
description: 前端安全防护和输入验证
globs: ["**/*.vue", "**/*.ts", "**/components/**/*", "**/utils/**/*"]
alwaysApply: false
---

# 前端安全规范

## 🛡️ 内容安全策略 (CSP)

### CSP 配置
```typescript
// utils/csp.ts
// ✅ 实施内容安全策略防止 XSS 攻击
const cspConfig = {
  'default-src': ["'self'"],
  'script-src': ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net"],
  'style-src': ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
  'font-src': ["'self'", "https://fonts.gstatic.com"],
  'img-src': ["'self'", "data:", "https:"],
  'connect-src': ["'self'", "https://api.example.com"],
  'frame-src': ["'none'"],
  'object-src': ["'none'"],
  'base-uri': ["'self'"],
  'form-action': ["'self'"],
};

// ✅ CSP 中间件实现
export const cspMiddleware = (req: Request, res: Response, next: NextFunction) => {
  const cspHeader = Object.entries(cspConfig)
    .map(([key, values]) => `${key} ${values.join(' ')}`)
    .join('; ');
  
  res.setHeader('Content-Security-Policy', cspHeader);
  next();
};
```

## 🔍 输入验证和转义

### 输入验证工具
```typescript
// utils/input-validation.ts
import DOMPurify from 'dompurify';
import { z } from 'zod';

// ✅ 输入验证 Schema
const userInputSchema = z.object({
  name: z.string().min(1).max(100).regex(/^[a-zA-Z\u4e00-\u9fa5\s]+$/),
  email: z.string().email(),
  message: z.string().min(1).max(1000),
});

// ✅ 输入清理函数
export const sanitizeUserInput = (input: unknown): string => {
  if (typeof input !== 'string') {
    throw new Error('输入必须是字符串类型');
  }
  
  // 移除潜在的危险字符
  const cleaned = input
    .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
    .replace(/javascript:/gi, '')
    .replace(/on\w+\s*=/gi, '');
  
  // 使用 DOMPurify 进一步清理
  return DOMPurify.sanitize(cleaned);
};

// ✅ Vue 组件中的输入处理
export const useInputValidation = () => {
  const validateAndSanitize = (input: unknown) => {
    try {
      const validated = userInputSchema.parse(input);
      return {
        name: sanitizeUserInput(validated.name),
        email: validated.email, // 邮箱格式已验证
        message: sanitizeUserInput(validated.message),
      };
    } catch (error) {
      throw new Error('输入验证失败');
    }
  };
  
  return { validateAndSanitize };
};
```

### Vue 组件安全示例
```vue
<template>
  <form @submit.prevent="handleSubmit">
    <div>
      <label for="name">姓名</label>
      <input
        id="name"
        v-model="formData.name"
        type="text"
        :class="{ 'error': errors.name }"
        @blur="validateField('name')"
      />
      <span v-if="errors.name" class="error-message">{{ errors.name }}</span>
    </div>
    
    <div>
      <label for="email">邮箱</label>
      <input
        id="email"
        v-model="formData.email"
        type="email"
        :class="{ 'error': errors.email }"
        @blur="validateField('email')"
      />
      <span v-if="errors.email" class="error-message">{{ errors.email }}</span>
    </div>
    
    <div>
      <label for="message">消息</label>
      <textarea
        id="message"
        v-model="formData.message"
        :class="{ 'error': errors.message }"
        @blur="validateField('message')"
      ></textarea>
      <span v-if="errors.message" class="error-message">{{ errors.message }}</span>
    </div>
    
    <button type="submit" :disabled="!isFormValid">提交</button>
  </form>
</template>

<script setup lang="ts">
import { useInputValidation } from '@/utils/input-validation';

const { validateAndSanitize } = useInputValidation();

const formData = reactive({
  name: '',
  email: '',
  message: '',
});

const errors = reactive({
  name: '',
  email: '',
  message: '',
});

const validateField = (field: keyof typeof formData) => {
  try {
    const validated = validateAndSanitize({ [field]: formData[field] });
    errors[field] = '';
  } catch (error) {
    errors[field] = error.message;
  }
};

const isFormValid = computed(() => {
  return Object.values(errors).every(error => !error) &&
         Object.values(formData).every(value => value.trim() !== '');
});

const handleSubmit = async () => {
  try {
    const validatedData = validateAndSanitize(formData);
    // 发送到服务器
    await submitForm(validatedData);
  } catch (error) {
    console.error('表单提交失败:', error);
  }
};
</script>
```

## 🔒 敏感信息保护

### 数据过滤
```typescript
// utils/data-filtering.ts
interface UserData {
  id: string;
  name: string;
  email: string;
  // ❌ 不要在客户端暴露敏感信息
  // password: string;
  // apiKey: string;
  // internalId: string;
}

// ✅ 敏感数据过滤
export const filterSensitiveData = (user: any): UserData => {
  const { password, apiKey, internalId, ...safeData } = user;
  return safeData as UserData;
};

// ✅ 环境变量保护
const config = {
  // ✅ 公开配置
  apiUrl: process.env.VITE_API_URL,
  appName: process.env.VITE_APP_NAME,
  
  // ❌ 不要在客户端暴露敏感配置
  // databaseUrl: process.env.DATABASE_URL,
  // jwtSecret: process.env.JWT_SECRET,
};
```

### 数据脱敏
```typescript
// utils/data-masking.ts
export class DataMaskingService {
  // ✅ 邮箱脱敏
  maskEmail(email: string): string {
    const [localPart, domain] = email.split('@');
    const maskedLocal = localPart.length > 2 
      ? localPart.substring(0, 2) + '*'.repeat(localPart.length - 2)
      : localPart;
    return `${maskedLocal}@${domain}`;
  }
  
  // ✅ 手机号脱敏
  maskPhone(phone: string): string {
    if (phone.length < 7) return phone;
    return phone.substring(0, 3) + '****' + phone.substring(phone.length - 4);
  }
  
  // ✅ 身份证号脱敏
  maskIdCard(idCard: string): string {
    if (idCard.length < 8) return idCard;
    return idCard.substring(0, 4) + '********' + idCard.substring(idCard.length - 4);
  }
  
  // ✅ 用户数据脱敏
  maskUserData(user: any): any {
    return {
      ...user,
      email: this.maskEmail(user.email),
      phone: user.phone ? this.maskPhone(user.phone) : undefined,
      idCard: user.idCard ? this.maskIdCard(user.idCard) : undefined,
    };
  }
}
```

## 🎯 前端安全最佳实践

### 必需配置项
- ✅ 实施内容安全策略 (CSP)
- ✅ 对用户输入进行验证和转义
- ✅ 使用 DOMPurify 清理 HTML 内容
- ✅ 保护敏感信息，不在客户端暴露
- ✅ 实现数据脱敏功能

### 参考文件
- `utils/csp.ts` - CSP 配置
- `utils/input-validation.ts` - 输入验证工具
- `utils/data-filtering.ts` - 数据过滤工具
- `utils/data-masking.ts` - 数据脱敏工具