security-review

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Security Review Skill

安全审查Skill

This skill ensures all code follows security best practices and identifies potential vulnerabilities.
该Skill可确保所有代码遵循安全最佳实践,并识别潜在的漏洞。

When to Activate

何时触发

  • Implementing authentication or authorization
  • Handling user input or file uploads
  • Creating new API endpoints
  • Working with secrets or credentials
  • Implementing payment features
  • Storing or transmitting sensitive data
  • Integrating third-party APIs
  • 实现身份认证或授权功能时
  • 处理用户输入或文件上传时
  • 创建新的API端点时
  • 处理密钥或凭证信息时
  • 实现支付相关功能时
  • 存储或传输敏感数据时
  • 集成第三方API时

Security Checklist

安全检查清单

1. Secrets Management

1. 密钥管理

❌ NEVER Do This

❌ 禁止这么做

javascript
const apiKey = "sk-proj-xxxxx"  // Hardcoded secret
const dbPassword = "password123" // In source code
javascript
const apiKey = "sk-proj-xxxxx"  // 硬编码密钥
const dbPassword = "password123" // 直接写在源码中

✅ ALWAYS Do This

✅ 务必这么做

javascript
const apiKey = process.env.OPENAI_API_KEY
const dbUrl = process.env.DATABASE_URL

// Verify secrets exist
if (!apiKey) {
  throw new Error('OPENAI_API_KEY not configured')
}
javascript
const apiKey = process.env.OPENAI_API_KEY
const dbUrl = process.env.DATABASE_URL

// 校验密钥是否存在
if (!apiKey) {
  throw new Error('OPENAI_API_KEY not configured')
}

Verification Steps

验证步骤

  • No hardcoded API keys, tokens, or passwords
  • All secrets in environment variables
  • .env.local
    in .gitignore
  • No secrets in git history
  • Production secrets in hosting platform (Vercel, Railway)
  • 无硬编码的API密钥、令牌或密码
  • 所有密钥都存储在环境变量中
  • .env.local
    已加入.gitignore
  • Git历史中无密钥信息
  • 生产环境密钥存储在托管平台(Vercel、Railway)

2. Input Validation

2. 输入验证

Always Validate User Input

始终校验用户输入

typescript
import { z } from 'zod'

// Define validation schema
const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  age: z.number().int().min(0).max(150)
})

// Validate before processing
export async function createUser(input: unknown) {
  try {
    const validated = CreateUserSchema.parse(input)
    return await db.users.create(validated)
  } catch (error) {
    if (error instanceof z.ZodError) {
      return { success: false, errors: error.errors }
    }
    throw error
  }
}
typescript
import { z } from 'zod'

// 定义校验 schema
const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  age: z.number().int().min(0).max(150)
})

// 处理前先校验
export async function createUser(input: unknown) {
  try {
    const validated = CreateUserSchema.parse(input)
    return await db.users.create(validated)
  } catch (error) {
    if (error instanceof z.ZodError) {
      return { success: false, errors: error.errors }
    }
    throw error
  }
}

File Upload Validation

文件上传校验

typescript
function validateFileUpload(file: File) {
  // Size check (5MB max)
  const maxSize = 5 * 1024 * 1024
  if (file.size > maxSize) {
    throw new Error('File too large (max 5MB)')
  }

  // Type check
  const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
  if (!allowedTypes.includes(file.type)) {
    throw new Error('Invalid file type')
  }

  // Extension check
  const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif']
  const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0]
  if (!extension || !allowedExtensions.includes(extension)) {
    throw new Error('Invalid file extension')
  }

  return true
}
typescript
function validateFileUpload(file: File) {
  // 大小校验(最大5MB)
  const maxSize = 5 * 1024 * 1024
  if (file.size > maxSize) {
    throw new Error('File too large (max 5MB)')
  }

  // 类型校验
  const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
  if (!allowedTypes.includes(file.type)) {
    throw new Error('Invalid file type')
  }

  // 后缀校验
  const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif']
  const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0]
  if (!extension || !allowedExtensions.includes(extension)) {
    throw new Error('Invalid file extension')
  }

  return true
}

Verification Steps

验证步骤

  • All user inputs validated with schemas
  • File uploads restricted (size, type, extension)
  • No direct use of user input in queries
  • Whitelist validation (not blacklist)
  • Error messages don't leak sensitive info
  • 所有用户输入都通过schema校验
  • 文件上传做了限制(大小、类型、后缀)
  • 不在查询中直接使用用户输入
  • 采用白名单校验(而非黑名单)
  • 错误信息不会泄露敏感信息

3. SQL Injection Prevention

3. SQL注入防护

❌ NEVER Concatenate SQL

❌ 禁止拼接SQL语句

javascript
// DANGEROUS - SQL Injection vulnerability
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
await db.query(query)
javascript
// 危险 - 存在SQL注入漏洞
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
await db.query(query)

✅ ALWAYS Use Parameterized Queries

✅ 始终使用参数化查询

javascript
// Safe - parameterized query
const { data } = await supabase
  .from('users')
  .select('*')
  .eq('email', userEmail)

// Or with raw SQL
await db.query(
  'SELECT * FROM users WHERE email = $1',
  [userEmail]
)
javascript
// 安全 - 参数化查询
const { data } = await supabase
  .from('users')
  .select('*')
  .eq('email', userEmail)

// 原生SQL写法
await db.query(
  'SELECT * FROM users WHERE email = $1',
  [userEmail]
)

Verification Steps

验证步骤

  • All database queries use parameterized queries
  • No string concatenation in SQL
  • ORM/query builder used correctly
  • Supabase queries properly sanitized
  • 所有数据库查询都使用参数化查询
  • SQL语句中没有字符串拼接
  • ORM/查询构建器使用正确
  • Supabase查询已正确做了 sanitize 处理

4. Authentication & Authorization

4. 身份认证与授权

JWT Token Handling

JWT令牌处理

javascript
// ❌ WRONG: localStorage (vulnerable to XSS)
localStorage.setItem('token', token)

// ✅ CORRECT: httpOnly cookies
res.setHeader('Set-Cookie',
  `token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
javascript
// ❌ 错误:存储在localStorage(存在XSS风险)
localStorage.setItem('token', token)

// ✅ 正确:使用httpOnly cookie
res.setHeader('Set-Cookie',
  `token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)

Authorization Checks

授权校验

typescript
export async function deleteUser(userId: string, requesterId: string) {
  // ALWAYS verify authorization first
  const requester = await db.users.findUnique({
    where: { id: requesterId }
  })

  if (requester.role !== 'admin') {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 403 }
    )
  }

  // Proceed with deletion
  await db.users.delete({ where: { id: userId } })
}
typescript
export async function deleteUser(userId: string, requesterId: string) {
  // 务必先校验权限
  const requester = await db.users.findUnique({
    where: { id: requesterId }
  })

  if (requester.role !== 'admin') {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 403 }
    )
  }

  // 执行删除操作
  await db.users.delete({ where: { id: userId } })
}

Row Level Security (Supabase)

行级安全(Supabase)

sql
-- Enable RLS on all tables
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

-- Users can only view their own data
CREATE POLICY "Users view own data"
  ON users FOR SELECT
  USING (auth.uid() = id);

-- Users can only update their own data
CREATE POLICY "Users update own data"
  ON users FOR UPDATE
  USING (auth.uid() = id);
sql
-- 为所有表开启RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

-- 用户仅可查看自己的数据
CREATE POLICY "Users view own data"
  ON users FOR SELECT
  USING (auth.uid() = id);

-- 用户仅可修改自己的数据
CREATE POLICY "Users update own data"
  ON users FOR UPDATE
  USING (auth.uid() = id);

Verification Steps

验证步骤

  • Tokens stored in httpOnly cookies (not localStorage)
  • Authorization checks before sensitive operations
  • Row Level Security enabled in Supabase
  • Role-based access control implemented
  • Session management secure
  • 令牌存储在httpOnly cookie中(而非localStorage)
  • 敏感操作前先做授权校验
  • Supabase已开启行级安全
  • 已实现基于角色的访问控制
  • 会话管理机制安全

5. XSS Prevention

5. XSS防护

Sanitize HTML

sanitize HTML内容

typescript
import DOMPurify from 'isomorphic-dompurify'

// ALWAYS sanitize user-provided HTML
function renderUserContent(html: string) {
  const clean = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
    ALLOWED_ATTR: []
  })
  return <div dangerouslySetInnerHTML={{ __html: clean }} />
}
typescript
import DOMPurify from 'isomorphic-dompurify'

// 始终对用户提供的HTML做sanitize处理
function renderUserContent(html: string) {
  const clean = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
    ALLOWED_ATTR: []
  })
  return <div dangerouslySetInnerHTML={{ __html: clean }} />
}

Content Security Policy

内容安全策略

javascript
// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: `
      default-src 'self';
      script-src 'self' 'unsafe-eval' 'unsafe-inline';
      style-src 'self' 'unsafe-inline';
      img-src 'self' data: https:;
      font-src 'self';
      connect-src 'self' https://api.example.com;
    `.replace(/\s{2,}/g, ' ').trim()
  }
]
javascript
// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: `
      default-src 'self';
      script-src 'self' 'unsafe-eval' 'unsafe-inline';
      style-src 'self' 'unsafe-inline';
      img-src 'self' data: https;
      font-src 'self';
      connect-src 'self' https://api.example.com;
    `.replace(/\s{2,}/g, ' ').trim()
  }
]

Verification Steps

验证步骤

  • User-provided HTML sanitized
  • CSP headers configured
  • No unvalidated dynamic content rendering
  • React's built-in XSS protection used
  • 用户提供的HTML已做sanitize处理
  • 已配置CSP响应头
  • 不存在未校验的动态内容渲染
  • 使用了React内置的XSS防护能力

6. CSRF Protection

6. CSRF防护

CSRF Tokens

CSRF令牌

typescript
import { csrf } from '@/lib/csrf'

export async function POST(request: Request) {
  const token = request.headers.get('X-CSRF-Token')

  if (!csrf.verify(token)) {
    return NextResponse.json(
      { error: 'Invalid CSRF token' },
      { status: 403 }
    )
  }

  // Process request
}
typescript
import { csrf } from '@/lib/csrf'

export async function POST(request: Request) {
  const token = request.headers.get('X-CSRF-Token')

  if (!csrf.verify(token)) {
    return NextResponse.json(
      { error: 'Invalid CSRF token' },
      { status: 403 }
    )
  }

  // 处理请求
}

SameSite Cookies

SameSite Cookie

javascript
res.setHeader('Set-Cookie',
  `session=${sessionId}; HttpOnly; Secure; SameSite=Strict`)
javascript
res.setHeader('Set-Cookie',
  `session=${sessionId}; HttpOnly; Secure; SameSite=Strict`)

Verification Steps

验证步骤

  • CSRF tokens on state-changing operations
  • SameSite=Strict on all cookies
  • Double-submit cookie pattern implemented
  • 修改状态的操作都校验了CSRF令牌
  • 所有Cookie都设置了SameSite=Strict
  • 已实现双重提交Cookie模式

7. Rate Limiting

7. 限流

API Rate Limiting

API限流

javascript
import rateLimit from 'express-rate-limit'

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  message: 'Too many requests'
})

// Apply to routes
app.use('/api/', limiter)
javascript
import rateLimit from 'express-rate-limit'

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100, // 每个窗口最多100次请求
  message: 'Too many requests'
})

// 应用到路由
app.use('/api/', limiter)

Expensive Operations

高消耗操作限流

javascript
// Aggressive rate limiting for searches
const searchLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 10, // 10 requests per minute
  message: 'Too many search requests'
})

app.use('/api/search', searchLimiter)
javascript
// 搜索接口使用更严格的限流
const searchLimiter = rateLimit({
  windowMs: 60 * 1000, // 1分钟
  max: 10, // 每分钟最多10次请求
  message: 'Too many search requests'
})

app.use('/api/search', searchLimiter)

Verification Steps

验证步骤

  • Rate limiting on all API endpoints
  • Stricter limits on expensive operations
  • IP-based rate limiting
  • User-based rate limiting (authenticated)
  • 所有API端点都配置了限流
  • 高消耗操作有更严格的限流规则
  • 基于IP的限流
  • 已实现认证用户的基于用户ID的限流

8. Sensitive Data Exposure

8. 敏感数据泄露防护

Logging

日志处理

javascript
// ❌ WRONG: Logging sensitive data
console.log('User login:', { email, password })
console.log('Payment:', { cardNumber, cvv })

// ✅ CORRECT: Redact sensitive data
console.log('User login:', { email, userId })
console.log('Payment:', { last4: card.last4, userId })
javascript
// ❌ 错误:记录敏感数据
console.log('User login:', { email, password })
console.log('Payment:', { cardNumber, cvv })

// ✅ 正确:脱敏敏感数据
console.log('User login:', { email, userId })
console.log('Payment:', { last4: card.last4, userId })

Error Messages

错误信息处理

javascript
// ❌ WRONG: Exposing internal details
catch (error) {
  return NextResponse.json(
    { error: error.message, stack: error.stack },
    { status: 500 }
  )
}

// ✅ CORRECT: Generic error messages
catch (error) {
  console.error('Internal error:', error)
  return NextResponse.json(
    { error: 'An error occurred. Please try again.' },
    { status: 500 }
  )
}
javascript
// ❌ 错误:暴露内部细节
catch (error) {
  return NextResponse.json(
    { error: error.message, stack: error.stack },
    { status: 500 }
  )
}

// ✅ 正确:返回通用错误信息
catch (error) {
  console.error('Internal error:', error)
  return NextResponse.json(
    { error: 'An error occurred. Please try again.' },
    { status: 500 }
  )
}

Verification Steps

验证步骤

  • No passwords, tokens, or secrets in logs
  • Error messages generic for users
  • Detailed errors only in server logs
  • No stack traces exposed to users
  • 日志中无密码、令牌、密钥信息
  • 面向用户的错误信息是通用描述
  • 仅服务端日志包含详细错误信息
  • 不会向用户暴露栈追踪信息

9. Blockchain Security (Solana)

9. 区块链安全(Solana)

Wallet Verification

钱包校验

typescript
import { verify } from '@solana/web3.js'

async function verifyWalletOwnership(
  publicKey: string,
  signature: string,
  message: string
) {
  try {
    const isValid = verify(
      Buffer.from(message),
      Buffer.from(signature, 'base64'),
      Buffer.from(publicKey, 'base64')
    )
    return isValid
  } catch (error) {
    return false
  }
}
typescript
import { verify } from '@solana/web3.js'

async function verifyWalletOwnership(
  publicKey: string,
  signature: string,
  message: string
) {
  try {
    const isValid = verify(
      Buffer.from(message),
      Buffer.from(signature, 'base64'),
      Buffer.from(publicKey, 'base64')
    )
    return isValid
  } catch (error) {
    return false
  }
}

Transaction Verification

交易校验

typescript
async function verifyTransaction(transaction: Transaction) {
  // Verify recipient
  if (transaction.to !== expectedRecipient) {
    throw new Error('Invalid recipient')
  }

  // Verify amount
  if (transaction.amount > maxAmount) {
    throw new Error('Amount exceeds limit')
  }

  // Verify user has sufficient balance
  const balance = await getBalance(transaction.from)
  if (balance < transaction.amount) {
    throw new Error('Insufficient balance')
  }

  return true
}
typescript
async function verifyTransaction(transaction: Transaction) {
  // 校验收款方
  if (transaction.to !== expectedRecipient) {
    throw new Error('Invalid recipient')
  }

  // 校验金额
  if (transaction.amount > maxAmount) {
    throw new Error('Amount exceeds limit')
  }

  // 校验用户余额充足
  const balance = await getBalance(transaction.from)
  if (balance < transaction.amount) {
    throw new Error('Insufficient balance')
  }

  return true
}

Verification Steps

验证步骤

  • Wallet signatures verified
  • Transaction details validated
  • Balance checks before transactions
  • No blind transaction signing
  • 已校验钱包签名
  • 已校验交易详情
  • 交易前做了余额检查
  • 无盲签交易逻辑

10. Dependency Security

10. 依赖安全

Regular Updates

定期更新

bash
undefined
bash
undefined

Check for vulnerabilities

检查漏洞

npm audit
npm audit

Fix automatically fixable issues

修复可自动处理的问题

npm audit fix
npm audit fix

Update dependencies

更新依赖

npm update
npm update

Check for outdated packages

检查过期的包

npm outdated
undefined
npm outdated
undefined

Lock Files

锁文件

bash
undefined
bash
undefined

ALWAYS commit lock files

务必提交锁文件

git add package-lock.json
git add package-lock.json

Use in CI/CD for reproducible builds

CI/CD中使用该命令实现可复现构建

npm ci # Instead of npm install
undefined
npm ci # 代替npm install
undefined

Verification Steps

验证步骤

  • Dependencies up to date
  • No known vulnerabilities (npm audit clean)
  • Lock files committed
  • Dependabot enabled on GitHub
  • Regular security updates
  • 依赖已更新到最新版本
  • 无已知漏洞(npm audit无告警)
  • 锁文件已提交到代码仓库
  • GitHub已开启Dependabot
  • 定期做安全更新

Security Testing

安全测试

Automated Security Tests

自动化安全测试

typescript
// Test authentication
test('requires authentication', async () => {
  const response = await fetch('/api/protected')
  expect(response.status).toBe(401)
})

// Test authorization
test('requires admin role', async () => {
  const response = await fetch('/api/admin', {
    headers: { Authorization: `Bearer ${userToken}` }
  })
  expect(response.status).toBe(403)
})

// Test input validation
test('rejects invalid input', async () => {
  const response = await fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify({ email: 'not-an-email' })
  })
  expect(response.status).toBe(400)
})

// Test rate limiting
test('enforces rate limits', async () => {
  const requests = Array(101).fill(null).map(() =>
    fetch('/api/endpoint')
  )

  const responses = await Promise.all(requests)
  const tooManyRequests = responses.filter(r => r.status === 429)

  expect(tooManyRequests.length).toBeGreaterThan(0)
})
typescript
// 测试身份认证
test('requires authentication', async () => {
  const response = await fetch('/api/protected')
  expect(response.status).toBe(401)
})

// 测试授权
test('requires admin role', async () => {
  const response = await fetch('/api/admin', {
    headers: { Authorization: `Bearer ${userToken}` }
  })
  expect(response.status).toBe(403)
})

// 测试输入校验
test('rejects invalid input', async () => {
  const response = await fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify({ email: 'not-an-email' })
  })
  expect(response.status).toBe(400)
})

// 测试限流
test('enforces rate limits', async () => {
  const requests = Array(101).fill(null).map(() =>
    fetch('/api/endpoint')
  )

  const responses = await Promise.all(requests)
  const tooManyRequests = responses.filter(r => r.status === 429)

  expect(tooManyRequests.length).toBeGreaterThan(0)
})

Pre-Deployment Security Checklist

部署前安全检查清单

Before ANY production deployment:
  • Secrets: No hardcoded secrets, all in env vars
  • Input Validation: All user inputs validated
  • SQL Injection: All queries parameterized
  • XSS: User content sanitized
  • CSRF: Protection enabled
  • Authentication: Proper token handling
  • Authorization: Role checks in place
  • Rate Limiting: Enabled on all endpoints
  • HTTPS: Enforced in production
  • Security Headers: CSP, X-Frame-Options configured
  • Error Handling: No sensitive data in errors
  • Logging: No sensitive data logged
  • Dependencies: Up to date, no vulnerabilities
  • Row Level Security: Enabled in Supabase
  • CORS: Properly configured
  • File Uploads: Validated (size, type)
  • Wallet Signatures: Verified (if blockchain)
所有生产环境部署前都要完成以下检查:
  • 密钥:无硬编码密钥,所有密钥都存储在环境变量中
  • 输入校验:所有用户输入都已校验
  • SQL注入:所有查询都使用参数化查询
  • XSS:用户内容已做sanitize处理
  • CSRF:已开启防护
  • 身份认证:令牌处理逻辑正确
  • 授权:已配置角色校验逻辑
  • 限流:所有端点都已开启限流
  • HTTPS:生产环境已强制HTTPS
  • 安全响应头:已配置CSP、X-Frame-Options等
  • 错误处理:错误中无敏感数据
  • 日志:无敏感数据记录
  • 依赖:已更新到最新版本,无漏洞
  • 行级安全:Supabase已开启行级安全
  • CORS:配置正确
  • 文件上传:已做校验(大小、类型)
  • 钱包签名:已做校验(如果涉及区块链)

Resources

参考资源


Remember: Security is not optional. One vulnerability can compromise the entire platform. When in doubt, err on the side of caution.

注意:安全不是可选项,一个漏洞就可能危及整个平台。如果不确定,优先选择更安全的方案。