security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Security Guidelines

API路由安全指南

Critical Security Rules

核心安全规则

🚨 NEVER commit code that bypasses these security requirements.
🚨 绝对不要提交违反这些安全要求的代码。

1. Authentication & Authorization Middleware

1. 身份验证与授权中间件

ALL API routes that handle user data MUST use appropriate middleware:
typescript
// ✅ CORRECT: Use withEmailAccount for email-scoped operations
export const GET = withEmailAccount(async (request, { params }) => {
  const { emailAccountId } = request.auth;
  // ...
});

// ✅ CORRECT: Use withAuth for user-scoped operations
export const GET = withAuth(async (request) => {
  const { userId } = request.auth;
  // ...
});

// ❌ WRONG: Direct access without authentication
export const GET = async (request) => {
  // This exposes data to unauthenticated users!
  const data = await prisma.user.findMany();
  return NextResponse.json(data);
};
所有处理用户数据的API路由必须使用合适的中间件:
typescript
// ✅ 正确:使用withEmailAccount处理邮箱账户范围的操作
export const GET = withEmailAccount(async (request, { params }) => {
  const { emailAccountId } = request.auth;
  // ...
});

// ✅ 正确:使用withAuth处理用户范围的操作
export const GET = withAuth(async (request) => {
  const { userId } = request.auth;
  // ...
});

// ❌ 错误:无身份验证直接访问
export const GET = async (request) => {
  // 这会向未认证用户暴露数据!
  const data = await prisma.user.findMany();
  return NextResponse.json(data);
};

2. Data Access Control

2. 数据访问控制

ALL database queries MUST be scoped to the authenticated user/account:
typescript
// ✅ CORRECT: Always include user/account filtering
const schedule = await prisma.schedule.findUnique({
  where: { 
    id: scheduleId, 
    emailAccountId  // 🔒 Critical: Ensures user owns this resource
  },
});

// ✅ CORRECT: Filter by user ownership
const rules = await prisma.rule.findMany({
  where: { 
    emailAccountId,  // 🔒 Only user's rules
    enabled: true 
  },
});

// ❌ WRONG: Missing user/account filtering
const schedule = await prisma.schedule.findUnique({
  where: { id: scheduleId }, // 🚨 Any user can access any schedule!
});
所有数据库查询必须限定在已认证用户/账户的范围内:
typescript
// ✅ 正确:始终包含用户/账户过滤条件
const schedule = await prisma.schedule.findUnique({
  where: { 
    id: scheduleId, 
    emailAccountId  // 🔒 关键:确保用户拥有该资源
  },
});

// ✅ 正确:按用户所有权过滤
const rules = await prisma.rule.findMany({
  where: { 
    emailAccountId,  // 🔒 仅返回该用户的规则
    enabled: true 
  },
});

// ❌ 错误:缺少用户/账户过滤条件
const schedule = await prisma.schedule.findUnique({
  where: { id: scheduleId }, // 🚨 任何用户都可以访问任何日程!
});

3. Resource Ownership Validation

3. 资源所有权验证

Always validate that resources belong to the authenticated user:
typescript
// ✅ CORRECT: Validate ownership before operations
async function updateRule({ ruleId, emailAccountId, data }) {
  const rule = await prisma.rule.findUnique({
    where: { 
      id: ruleId, 
      emailAccount: { id: emailAccountId } // 🔒 Ownership check
    },
  });
  
  if (!rule) throw new SafeError("Rule not found"); // Returns 404, doesn't leak existence
  
  return prisma.rule.update({
    where: { id: ruleId },
    data,
  });
}

// ❌ WRONG: Direct updates without ownership validation
async function updateRule({ ruleId, data }) {
  return prisma.rule.update({
    where: { id: ruleId }, // 🚨 User can modify any rule!
    data,
  });
}

始终验证资源是否属于已认证用户:
typescript
// ✅ 正确:在操作前验证所有权
async function updateRule({ ruleId, emailAccountId, data }) {
  const rule = await prisma.rule.findUnique({
    where: { 
      id: ruleId, 
      emailAccount: { id: emailAccountId } // 🔒 所有权校验
    },
  });
  
  if (!rule) throw new SafeError("Rule not found"); // 返回404,不泄露资源存在性
  
  return prisma.rule.update({
    where: { id: ruleId },
    data,
  });
}

// ❌ 错误:无所有权验证直接更新
async function updateRule({ ruleId, data }) {
  return prisma.rule.update({
    where: { id: ruleId }, // 🚨 用户可以修改任何规则!
    data,
  });
}

Middleware Usage Guidelines

中间件使用指南

When to use
withEmailAccount

何时使用
withEmailAccount

Use for operations that are scoped to a specific email account:
  • Reading/writing emails, rules, schedules, etc.
  • Any operation that uses
    emailAccountId
typescript
export const GET = withEmailAccount(async (request) => {
  const { emailAccountId, userId, email } = request.auth;
  // All three fields available
});
用于限定在特定邮箱账户范围内的操作:
  • 读取/写入邮件、规则、日程等
  • 任何使用
    emailAccountId
    的操作
typescript
export const GET = withEmailAccount(async (request) => {
  const { emailAccountId, userId, email } = request.auth;
  // 三个字段均可用
});

When to use
withAuth

何时使用
withAuth

Use for user-level operations:
  • User settings, API keys, referrals
  • Operations that use only
    userId
typescript
export const GET = withAuth(async (request) => {
  const { userId } = request.auth;
  // Only userId available
});
用于用户级别的操作:
  • 用户设置、API密钥、推荐码
  • 仅使用
    userId
    的操作
typescript
export const GET = withAuth(async (request) => {
  const { userId } = request.auth;
  // 仅userId可用
});

When to use
withError
only

何时仅使用
withError

Use for public endpoints or custom authentication:
  • Public webhooks (with separate validation)
  • Endpoints with custom auth logic
  • Cron endpoints (MUST use
    hasCronSecret
    )
typescript
// ✅ CORRECT: Public endpoint with custom auth
export const GET = withError(async (request) => {
  const session = await auth();
  if (!session?.user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
});

// ✅ CORRECT: Cron endpoint with secret validation
export const POST = withError(async (request) => {
  if (!hasCronSecret(request)) {
    captureException(new Error("Unauthorized cron request"));
    return new Response("Unauthorized", { status: 401 });
  }
  // ... cron logic
});

// ❌ WRONG: Cron endpoint without validation
export const POST = withError(async (request) => {
  // 🚨 Anyone can trigger this cron job!
  await sendDigestEmails();
});

用于公开端点或自定义身份验证场景:
  • 公开Webhook(需单独验证)
  • 带有自定义认证逻辑的端点
  • 定时任务端点(必须使用
    hasCronSecret
typescript
// ✅ 正确:带自定义认证的公开端点
export const GET = withError(async (request) => {
  const session = await auth();
  if (!session?.user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
});

// ✅ 正确:带密钥验证的定时任务端点
export const POST = withError(async (request) => {
  if (!hasCronSecret(request)) {
    captureException(new Error("Unauthorized cron request"));
    return new Response("Unauthorized", { status: 401 });
  }
  // ... 定时任务逻辑
});

// ❌ 错误:无验证的定时任务端点
export const POST = withError(async (request) => {
  // 🚨 任何人都可以触发这个定时任务!
  await sendDigestEmails();
});

Cron Endpoint Security

定时任务端点安全

🚨 CRITICAL: Cron endpoints without proper authentication can be triggered by anyone!
🚨 关键提示:未做适当身份验证的定时任务端点可能被任何人触发!

Cron Authentication Patterns

定时任务身份验证模式

typescript
// ✅ CORRECT: GET cron endpoint
export const GET = withError(async (request) => {
  if (!hasCronSecret(request)) {
    captureException(new Error("Unauthorized cron request"));
    return new Response("Unauthorized", { status: 401 });
  }
  
  // Safe to execute cron logic
  await processScheduledTasks();
  return NextResponse.json({ success: true });
});

// ✅ CORRECT: POST cron endpoint
export const POST = withError(async (request) => {
  if (!(await hasPostCronSecret(request))) {
    captureException(new Error("Unauthorized cron request"));
    return new Response("Unauthorized", { status: 401 });
  }
  
  // Safe to execute cron logic
  await processBulkOperations();
  return NextResponse.json({ success: true });
});
typescript
// ✅ 正确:GET类型定时任务端点
export const GET = withError(async (request) => {
  if (!hasCronSecret(request)) {
    captureException(new Error("Unauthorized cron request"));
    return new Response("Unauthorized", { status: 401 });
  }
  
  // 安全执行定时任务逻辑
  await processScheduledTasks();
  return NextResponse.json({ success: true });
});

// ✅ 正确:POST类型定时任务端点
export const POST = withError(async (request) => {
  if (!(await hasPostCronSecret(request))) {
    captureException(new Error("Unauthorized cron request"));
    return new Response("Unauthorized", { status: 401 });
  }
  
  // 安全执行定时任务逻辑
  await processBulkOperations();
  return NextResponse.json({ success: true });
});

Cron Security Checklist

定时任务安全检查清单

For any endpoint that performs automated tasks:
  • Uses
    withError
    middleware (not
    withAuth
    or
    withEmailAccount
    )
  • Validates cron secret using
    hasCronSecret(request)
    or
    hasPostCronSecret(request)
  • Captures unauthorized attempts with
    captureException
  • Returns
    401
    status for unauthorized requests
  • Contains bulk operations, scheduled tasks, or system maintenance
对于任何执行自动化任务的端点:
  • 使用
    withError
    中间件(而非
    withAuth
    withEmailAccount
  • 使用
    hasCronSecret(request)
    hasPostCronSecret(request)
    验证定时任务密钥
  • 使用
    captureException
    记录未授权访问尝试
  • 对未授权请求返回
    401
    状态码
  • 包含批量操作、定时任务或系统维护逻辑

Common Cron Endpoint Patterns

常见定时任务端点模式

typescript
// Digest/summary emails
export const POST = withError(async (request) => {
  if (!hasCronSecret(request)) {
    captureException(new Error("Unauthorized cron request: digest"));
    return new Response("Unauthorized", { status: 401 });
  }
  await sendDigestEmails();
});

// Cleanup operations
export const POST = withError(async (request) => {
  if (!(await hasPostCronSecret(request))) {
    captureException(new Error("Unauthorized cron request: cleanup"));
    return new Response("Unauthorized", { status: 401 });
  }
  await cleanupExpiredData();
});

// System monitoring
export const GET = withError(async (request) => {
  if (!hasCronSecret(request)) {
    captureException(new Error("Unauthorized cron request: monitor"));
    return new Response("Unauthorized", { status: 401 });
  }
  await monitorSystemHealth();
});
typescript
// 摘要/汇总邮件
export const POST = withError(async (request) => {
  if (!hasCronSecret(request)) {
    captureException(new Error("Unauthorized cron request: digest"));
    return new Response("Unauthorized", { status: 401 });
  }
  await sendDigestEmails();
});

// 清理操作
export const POST = withError(async (request) => {
  if (!(await hasPostCronSecret(request))) {
    captureException(new Error("Unauthorized cron request: cleanup"));
    return new Response("Unauthorized", { status: 401 });
  }
  await cleanupExpiredData();
});

// 系统监控
export const GET = withError(async (request) => {
  if (!hasCronSecret(request)) {
    captureException(new Error("Unauthorized cron request: monitor"));
    return new Response("Unauthorized", { status: 401 });
  }
  await monitorSystemHealth();
});

Environment Setup

环境配置

Ensure
CRON_SECRET
is properly configured:
bash
undefined
确保
CRON_SECRET
已正确配置:
bash
undefined

.env.local

.env.local

CRON_SECRET=your-secure-random-secret-here

**⚠️ Never use predictable cron secrets like:**
- `"secret"`
- `"password"`
- `"cron"`
- Short or simple strings

---
CRON_SECRET=your-secure-random-secret-here

**⚠️ 绝对不要使用可预测的定时任务密钥,例如:**
- `"secret"`
- `"password"`
- `"cron"`
- 短或简单的字符串

---

Database Security Patterns

数据库安全模式

✅ Secure Query Patterns

✅ 安全查询模式

typescript
// User-scoped queries
const user = await prisma.user.findUnique({
  where: { id: userId },
  select: { id: true, email: true } // Only return needed fields
});

// Email account-scoped queries
const emailAccount = await prisma.emailAccount.findUnique({
  where: { id: emailAccountId, userId }, // Double validation
});

// Related resource queries with ownership
const rule = await prisma.rule.findUnique({
  where: { 
    id: ruleId, 
    emailAccount: { id: emailAccountId } 
  },
  include: { actions: true }
});

// Filtered list queries
const schedules = await prisma.schedule.findMany({
  where: { emailAccountId },
  orderBy: { createdAt: 'desc' }
});
typescript
// 用户范围查询
const user = await prisma.user.findUnique({
  where: { id: userId },
  select: { id: true, email: true } // 仅返回所需字段
});

// 邮箱账户范围查询
const emailAccount = await prisma.emailAccount.findUnique({
  where: { id: emailAccountId, userId }, // 双重验证
});

// 带所有权校验的关联资源查询
const rule = await prisma.rule.findUnique({
  where: { 
    id: ruleId, 
    emailAccount: { id: emailAccountId } 
  },
  include: { actions: true }
});

// 过滤后的列表查询
const schedules = await prisma.schedule.findMany({
  where: { emailAccountId },
  orderBy: { createdAt: 'desc' }
});

❌ Insecure Query Patterns

❌ 不安全查询模式

typescript
// Missing user scoping
const schedules = await prisma.schedule.findMany(); // 🚨 Returns ALL schedules

// Missing ownership validation
const rule = await prisma.rule.findUnique({
  where: { id: ruleId } // 🚨 Can access any user's rule
});

// Exposed sensitive fields
const user = await prisma.user.findUnique({
  where: { id: userId }
  // 🚨 Returns ALL fields including sensitive data
});

// Direct parameter usage
const userId = request.nextUrl.searchParams.get('userId');
const user = await prisma.user.findUnique({
  where: { id: userId } // 🚨 User can access any user by changing URL
});

typescript
// 缺少用户范围限定
const schedules = await prisma.schedule.findMany(); // 🚨 返回所有日程

// 缺少所有权验证
const rule = await prisma.rule.findUnique({
  where: { id: ruleId } // 🚨 可以访问任何用户的规则
});

// 暴露敏感字段
const user = await prisma.user.findUnique({
  where: { id: userId }
  // 🚨 返回所有字段,包括敏感数据
});

// 直接使用参数
const userId = request.nextUrl.searchParams.get('userId');
const user = await prisma.user.findUnique({
  where: { id: userId } // 🚨 用户可通过修改URL访问任何用户数据
});

Input Validation & Sanitization

输入验证与清理

Parameter Validation

参数验证

typescript
// ✅ CORRECT: Validate all inputs
export const GET = withEmailAccount(async (request, { params }) => {
  const { id } = await params;
  
  if (!id) {
    return NextResponse.json(
      { error: "Missing schedule ID" }, 
      { status: 400 }
    );
  }
  
  // Additional validation
  if (typeof id !== 'string' || id.length < 10) {
    return NextResponse.json(
      { error: "Invalid schedule ID format" }, 
      { status: 400 }
    );
  }
});

// ❌ WRONG: Using parameters without validation
export const GET = withEmailAccount(async (request, { params }) => {
  const { id } = await params;
  // 🚨 Direct usage without validation
  const schedule = await prisma.schedule.findUnique({ where: { id } });
});
typescript
// ✅ 正确:验证所有输入
export const GET = withEmailAccount(async (request, { params }) => {
  const { id } = await params;
  
  if (!id) {
    return NextResponse.json(
      { error: "缺少日程ID" }, 
      { status: 400 }
    );
  }
  
  // 额外验证
  if (typeof id !== 'string' || id.length < 10) {
    return NextResponse.json(
      { error: "无效的日程ID格式" }, 
      { status: 400 }
    );
  }
});

// ❌ 错误:未验证直接使用参数
export const GET = withEmailAccount(async (request, { params }) => {
  const { id } = await params;
  // 🚨 直接使用未验证的参数
  const schedule = await prisma.schedule.findUnique({ where: { id } });
});

Body Validation with Zod

使用Zod验证请求体

typescript
// ✅ CORRECT: Always validate request bodies
const updateRuleSchema = z.object({
  name: z.string().min(1).max(100),
  enabled: z.boolean(),
  conditions: z.array(z.object({
    type: z.enum(['FROM', 'SUBJECT', 'BODY']),
    value: z.string().min(1)
  }))
});

export const PUT = withEmailAccount(async (request) => {
  const body = await request.json();
  const validatedData = updateRuleSchema.parse(body); // Throws on invalid data
  
  // Use validatedData, not body
});

typescript
// ✅ 正确:始终验证请求体
const updateRuleSchema = z.object({
  name: z.string().min(1).max(100),
  enabled: z.boolean(),
  conditions: z.array(z.object({
    type: z.enum(['FROM', 'SUBJECT', 'BODY']),
    value: z.string().min(1)
  }))
});

export const PUT = withEmailAccount(async (request) => {
  const body = await request.json();
  const validatedData = updateRuleSchema.parse(body); // 数据无效时抛出错误
  
  // 使用validatedData而非原始body
});

Error Handling Security

错误处理安全

Information Disclosure Prevention

防止信息泄露

typescript
// ✅ CORRECT: Safe error responses
if (!rule) {
  throw new SafeError("Rule not found"); // Generic 404
}

if (!hasPermission) {
  throw new SafeError("Access denied"); // Generic 403
}

// ❌ WRONG: Information disclosure
if (!rule) {
  throw new Error(`Rule ${ruleId} does not exist for user ${userId}`); 
  // 🚨 Reveals internal IDs and logic
}

if (!rule.emailAccountId === emailAccountId) {
  throw new Error("This rule belongs to a different account");
  // 🚨 Confirms existence of rule and reveals ownership info
}
typescript
// ✅ 正确:安全的错误响应
if (!rule) {
  throw new SafeError("Rule not found"); // 通用404错误
}

if (!hasPermission) {
  throw new SafeError("Access denied"); // 通用403错误
}

// ❌ 错误:信息泄露
if (!rule) {
  throw new Error(`Rule ${ruleId} does not exist for user ${userId}`); 
  // 🚨 泄露内部ID和逻辑
}

if (!rule.emailAccountId === emailAccountId) {
  throw new Error("This rule belongs to a different account");
  // 🚨 确认规则存在并泄露所有权信息
}

Consistent Error Responses

一致的错误响应

typescript
// ✅ CORRECT: Consistent error format
export const GET = withEmailAccount(async (request) => {
  try {
    // ... operation
  } catch (error) {
    if (error instanceof SafeError) {
      return NextResponse.json(
        { error: error.message, isKnownError: true },
        { status: error.statusCode || 400 }
      );
    }
    // Let middleware handle unexpected errors
    throw error;
  }
});

typescript
// ✅ 正确:统一的错误格式
export const GET = withEmailAccount(async (request) => {
  try {
    // ... 操作逻辑
  } catch (error) {
    if (error instanceof SafeError) {
      return NextResponse.json(
        { error: error.message, isKnownError: true },
        { status: error.statusCode || 400 }
      );
    }
    // 让中间件处理意外错误
    throw error;
  }
});

Common Security Vulnerabilities

常见安全漏洞

1. Insecure Direct Object References (IDOR)

1. 不安全的直接对象引用(IDOR)

typescript
// ❌ VULNERABLE: User can access any rule by changing ID
export const GET = async (request, { params }) => {
  const { ruleId } = await params;
  const rule = await prisma.rule.findUnique({ where: { id: ruleId } });
  return NextResponse.json(rule);
};

// ✅ SECURE: Always validate ownership
export const GET = withEmailAccount(async (request, { params }) => {
  const { emailAccountId } = request.auth;
  const { ruleId } = await params;
  
  const rule = await prisma.rule.findUnique({
    where: { 
      id: ruleId, 
      emailAccount: { id: emailAccountId } // 🔒 Ownership validation
    }
  });
  
  if (!rule) throw new SafeError("Rule not found");
  return NextResponse.json(rule);
});
typescript
// ❌ 存在漏洞:用户可通过修改ID访问任何规则
export const GET = async (request, { params }) => {
  const { ruleId } = await params;
  const rule = await prisma.rule.findUnique({ where: { id: ruleId } });
  return NextResponse.json(rule);
};

// ✅ 安全:始终验证所有权
export const GET = withEmailAccount(async (request, { params }) => {
  const { emailAccountId } = request.auth;
  const { ruleId } = await params;
  
  const rule = await prisma.rule.findUnique({
    where: { 
      id: ruleId, 
      emailAccount: { id: emailAccountId } // 🔒 所有权验证
    }
  });
  
  if (!rule) throw new SafeError("Rule not found");
  return NextResponse.json(rule);
});

2. Mass Assignment

2. 批量赋值

typescript
// ❌ VULNERABLE: User can modify any field
export const PUT = withEmailAccount(async (request) => {
  const body = await request.json();
  const rule = await prisma.rule.update({
    where: { id: body.id },
    data: body // 🚨 User controls all fields, including ownership!
  });
});

// ✅ SECURE: Explicitly allow only safe fields
const updateSchema = z.object({
  name: z.string(),
  enabled: z.boolean(),
  // Only allow specific fields
});

export const PUT = withEmailAccount(async (request) => {
  const body = await request.json();
  const validatedData = updateSchema.parse(body);
  
  const rule = await prisma.rule.update({
    where: { 
      id: ruleId,
      emailAccount: { id: emailAccountId } // Maintain ownership
    },
    data: validatedData // Only validated fields
  });
});
typescript
// ❌ 存在漏洞:用户可修改任何字段
export const PUT = withEmailAccount(async (request) => {
  const body = await request.json();
  const rule = await prisma.rule.update({
    where: { id: body.id },
    data: body // 🚨 用户控制所有字段,包括所有权!
  });
});

// ✅ 安全:仅显式允许安全字段
const updateSchema = z.object({
  name: z.string(),
  enabled: z.boolean(),
  // 仅允许特定字段
});

export const PUT = withEmailAccount(async (request) => {
  const body = await request.json();
  const validatedData = updateSchema.parse(body);
  
  const rule = await prisma.rule.update({
    where: { 
      id: ruleId,
      emailAccount: { id: emailAccountId } // 保持所有权校验
    },
    data: validatedData // 仅使用已验证的字段
  });
});

3. Privilege Escalation

3. 权限提升

typescript
// ❌ VULNERABLE: User can modify admin-only fields
const rule = await prisma.rule.update({
  where: { id: ruleId },
  data: {
    ...updateData,
    // 🚨 What if updateData contains system fields?
    ownerId: 'different-user-id', // User changes ownership!
    systemGenerated: false, // User modifies system flags!
  }
});

// ✅ SECURE: Whitelist approach
const allowedFields = {
  name: updateData.name,
  enabled: updateData.enabled,
  instructions: updateData.instructions,
  // Only explicitly allowed fields
};

const rule = await prisma.rule.update({
  where: { 
    id: ruleId,
    emailAccount: { id: emailAccountId } 
  },
  data: allowedFields
});
typescript
// ❌ 存在漏洞:用户可修改管理员专属字段
const rule = await prisma.rule.update({
  where: { id: ruleId },
  data: {
    ...updateData,
    // 🚨 如果updateData包含系统字段会怎样?
    ownerId: 'different-user-id', // 用户更改所有权!
    systemGenerated: false, // 用户修改系统标志!
  }
});

// ✅ 安全:白名单方式
const allowedFields = {
  name: updateData.name,
  enabled: updateData.enabled,
  instructions: updateData.instructions,
  // 仅显式允许的字段
};

const rule = await prisma.rule.update({
  where: { 
    id: ruleId,
    emailAccount: { id: emailAccountId } 
  },
  data: allowedFields
});

4. Unprotected Cron Endpoints

4. 未受保护的定时任务端点

typescript
// ❌ VULNERABLE: Anyone can trigger cron operations
export const POST = withError(async (request) => {
  // 🚨 No authentication - anyone can send digest emails!
  await sendDigestEmailsToAllUsers();
  return NextResponse.json({ success: true });
});

// ❌ VULNERABLE: Weak cron validation
export const POST = withError(async (request) => {
  const body = await request.json();
  if (body.secret !== "simple-password") { // 🚨 Predictable secret
    return new Response("Unauthorized", { status: 401 });
  }
  await performSystemMaintenance();
});

// ✅ SECURE: Proper cron authentication
export const POST = withError(async (request) => {
  if (!hasCronSecret(request)) { // 🔒 Strong secret validation
    captureException(new Error("Unauthorized cron request"));
    return new Response("Unauthorized", { status: 401 });
  }
  await performSystemMaintenance();
});

typescript
// ❌ 存在漏洞:任何人都可以触发定时任务操作
export const POST = withError(async (request) => {
  // 🚨 无身份验证 - 任何人都可以发送摘要邮件!
  await sendDigestEmailsToAllUsers();
  return NextResponse.json({ success: true });
});

// ❌ 存在漏洞:弱定时任务验证
export const POST = withError(async (request) => {
  const body = await request.json();
  if (body.secret !== "simple-password") { // 🚨 可预测的密钥
    return new Response("Unauthorized", { status: 401 });
  }
  await performSystemMaintenance();
});

// ✅ 安全:正确的定时任务身份验证
export const POST = withError(async (request) => {
  if (!hasCronSecret(request)) { // 🔒 强密钥验证
    captureException(new Error("Unauthorized cron request"));
    return new Response("Unauthorized", { status: 401 });
  }
  await performSystemMaintenance();
});

Security Checklist for API Routes

API路由安全检查清单

Before deploying any API route, verify:
部署任何API路由前,请验证:

Authentication ✅

身份验证 ✅

  • Uses appropriate middleware (
    withAuth
    or
    withEmailAccount
    )
  • Or uses
    withError
    with proper validation (cron endpoints, webhooks)
  • Cron endpoints use
    hasCronSecret()
    or
    hasPostCronSecret()
  • No public access to user data
  • Session/token validation is enforced
  • 使用了合适的中间件(
    withAuth
    withEmailAccount
  • 或使用
    withError
    并配置了正确的验证(定时任务端点、Webhook)
  • 定时任务端点使用了
    hasCronSecret()
    hasPostCronSecret()
  • 用户数据无公开访问权限
  • 会话/令牌验证已强制执行

Authorization ✅

授权 ✅

  • All queries include user/account filtering
  • Resource ownership is validated before operations
  • No direct object references without ownership checks
  • 所有查询均包含用户/账户过滤条件
  • 资源操作前已验证所有权
  • 无所有权校验的直接对象引用

Input Validation ✅

输入验证 ✅

  • All parameters are validated (type, format, length)
  • Request bodies use Zod schemas
  • SQL injection prevention (using Prisma correctly)
  • No user input directly in queries
  • 所有参数均已验证(类型、格式、长度)
  • 请求体使用Zod schema验证
  • 防止SQL注入(正确使用Prisma)
  • 查询中未直接使用用户输入

Data Protection ✅

数据保护 ✅

  • Only necessary fields are returned
  • Sensitive data is not exposed in responses
  • Error messages don't leak information
  • Consistent error response format
  • 仅返回必要字段
  • 响应中未暴露敏感数据
  • 错误信息未泄露内部信息
  • 统一的错误响应格式

Query Security ✅

查询安全 ✅

  • All
    findUnique
    /
    findFirst
    calls include ownership filters
  • All
    findMany
    calls are scoped to user's data
  • No queries return data from other users
  • Proper use of Prisma relationships for access control

  • 所有
    findUnique
    /
    findFirst
    调用均包含所有权过滤条件
  • 所有
    findMany
    调用均限定在用户数据范围内
  • 无查询返回其他用户的数据
  • 正确使用Prisma关联关系实现访问控制

Examples from Codebase

代码库示例

✅ Good Examples

✅ 优秀示例

Frequency API -
apps/web/app/api/user/frequency/[id]/route.ts
typescript
export const GET = withEmailAccount(async (request, { params }) => {
  const emailAccountId = request.auth.emailAccountId;
  const { id } = await params;
  
  if (!id) return NextResponse.json({ error: "Missing frequency id" }, { status: 400 });

  const schedule = await prisma.schedule.findUnique({
    where: { id, emailAccountId }, // 🔒 Scoped to user's account
  });

  if (!schedule) {
    return NextResponse.json({ error: "Schedule not found" }, { status: 404 });
  }

  return NextResponse.json(schedule);
});
Rules API -
apps/web/app/api/user/rules/[id]/route.ts
typescript
const rule = await prisma.rule.findUnique({
  where: { 
    id: ruleId, 
    emailAccount: { id: emailAccountId } // 🔒 Relationship-based ownership check
  },
  include: { actions: true, categoryFilters: true },
});
频率API -
apps/web/app/api/user/frequency/[id]/route.ts
typescript
export const GET = withEmailAccount(async (request, { params }) => {
  const emailAccountId = request.auth.emailAccountId;
  const { id } = await params;
  
  if (!id) return NextResponse.json({ error: "Missing frequency id" }, { status: 400 });

  const schedule = await prisma.schedule.findUnique({
    where: { id, emailAccountId }, // 🔒 限定在用户账户范围内
  });

  if (!schedule) {
    return NextResponse.json({ error: "Schedule not found" }, { status: 404 });
  }

  return NextResponse.json(schedule);
});
规则API -
apps/web/app/api/user/rules/[id]/route.ts
typescript
const rule = await prisma.rule.findUnique({
  where: { 
    id: ruleId, 
    emailAccount: { id: emailAccountId } // 🔒 基于关联关系的所有权校验
  },
  include: { actions: true, categoryFilters: true },
});

Areas for Security Review

安全审查重点

When reviewing code, pay special attention to:
  1. New API routes - Ensure proper middleware usage
  2. Database queries - Verify user scoping
  3. Parameter handling - Check validation and sanitization
  4. Error responses - Ensure no information disclosure
  5. Bulk operations - Extra care for mass updates/deletes

审查代码时,请特别关注:
  1. 新API路由 - 确保使用了正确的中间件
  2. 数据库查询 - 验证用户范围限定
  3. 参数处理 - 检查验证与清理逻辑
  4. 错误响应 - 确保无信息泄露
  5. 批量操作 - 对批量更新/删除需额外谨慎

Security Testing

安全测试

Manual Testing Checklist

手动测试清单

  1. Authentication bypass: Try accessing endpoints without auth headers
  2. IDOR testing: Modify resource IDs to access other users' data
  3. Parameter manipulation: Test with invalid/malicious parameters
  4. Error information: Check if errors reveal sensitive information
  1. 身份验证绕过:尝试在无授权头的情况下访问端点
  2. IDOR测试:修改资源ID以访问其他用户的数据
  3. 参数篡改:测试无效/恶意参数
  4. 错误信息:检查错误是否泄露敏感信息

Automated Security Tests

自动化安全测试

Include security tests in your test suites:
typescript
describe("Security Tests", () => {
  it("should not allow access without authentication", async () => {
    const response = await request.get("/api/user/rules/123");
    expect(response.status).toBe(401);
  });

  it("should not allow access to other users' resources", async () => {
    const response = await request
      .get("/api/user/rules/other-user-rule-id")
      .set("Authorization", "Bearer valid-token")
      .set("X-Email-Account-ID", "user-account-id");
    
    expect(response.status).toBe(404); // Not 403, to avoid info disclosure
  });
});

在测试套件中加入安全测试:
typescript
describe("Security Tests", () => {
  it("should not allow access without authentication", async () => {
    const response = await request.get("/api/user/rules/123");
    expect(response.status).toBe(401);
  });

  it("should not allow access to other users' resources", async () => {
    const response = await request
      .get("/api/user/rules/other-user-rule-id")
      .set("Authorization", "Bearer valid-token")
      .set("X-Email-Account-ID", "user-account-id");
    
    expect(response.status).toBe(404); // 避免返回403以防止信息泄露
  });
});

Deployment Security

部署安全

Environment Variables

环境变量

  • All sensitive data in environment variables
  • No secrets in code or version control
  • Different secrets for different environments
  • 所有敏感数据均存储在环境变量中
  • 代码或版本控制中无密钥
  • 不同环境使用不同密钥

Monitoring & Logging

监控与日志

  • Security events are logged
  • Failed authentication attempts tracked
  • Unusual access patterns monitored
  • No sensitive data in logs

Remember: Security is not optional. Every API route that handles user data must follow these guidelines. When in doubt, err on the side of caution and add extra security checks.
  • 安全事件已记录
  • 失败的身份验证尝试已追踪
  • 异常访问模式已监控
  • 日志中无敏感数据

记住:安全不是可选的。所有处理用户数据的API路由必须遵循这些指南。如有疑问,请谨慎处理,添加额外的安全检查。