security
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSecurity 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何时使用withEmailAccount
withEmailAccountUse 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何时使用withAuth
withAuthUse 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何时仅使用withError
withErrorUse 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 middleware (not
withErrororwithAuth)withEmailAccount - Validates cron secret using or
hasCronSecret(request)hasPostCronSecret(request) - Captures unauthorized attempts with
captureException - Returns status for unauthorized requests
401 - 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 is properly configured:
CRON_SECRETbash
undefined确保已正确配置:
CRON_SECRETbash
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 (or
withAuth)withEmailAccount - Or uses with proper validation (cron endpoints, webhooks)
withError - Cron endpoints use or
hasCronSecret()hasPostCronSecret() - No public access to user data
- Session/token validation is enforced
- 使用了合适的中间件(或
withAuth)withEmailAccount - 或使用并配置了正确的验证(定时任务端点、Webhook)
withError - 定时任务端点使用了或
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 /
findUniquecalls include ownership filtersfindFirst - All calls are scoped to user's data
findMany - 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.tstypescript
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.tstypescript
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.tstypescript
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.tstypescript
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:
- New API routes - Ensure proper middleware usage
- Database queries - Verify user scoping
- Parameter handling - Check validation and sanitization
- Error responses - Ensure no information disclosure
- Bulk operations - Extra care for mass updates/deletes
审查代码时,请特别关注:
- 新API路由 - 确保使用了正确的中间件
- 数据库查询 - 验证用户范围限定
- 参数处理 - 检查验证与清理逻辑
- 错误响应 - 确保无信息泄露
- 批量操作 - 对批量更新/删除需额外谨慎
Security Testing
安全测试
Manual Testing Checklist
手动测试清单
- Authentication bypass: Try accessing endpoints without auth headers
- IDOR testing: Modify resource IDs to access other users' data
- Parameter manipulation: Test with invalid/malicious parameters
- Error information: Check if errors reveal sensitive information
- 身份验证绕过:尝试在无授权头的情况下访问端点
- IDOR测试:修改资源ID以访问其他用户的数据
- 参数篡改:测试无效/恶意参数
- 错误信息:检查错误是否泄露敏感信息
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路由必须遵循这些指南。如有疑问,请谨慎处理,添加额外的安全检查。