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

❌ 绝对禁止

typescript
const apiKey = "sk-proj-xxxxx"  // Hardcoded secret
const dbPassword = "password123" // In source code
typescript
const apiKey = "sk-proj-xxxxx"  // Hardcoded secret
const dbPassword = "password123" // In source code

✅ ALWAYS Do This

✅ 正确做法

typescript
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')
}
typescript
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')
}

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'

// 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
  }
}

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) {
  // 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
}

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

typescript
// DANGEROUS - SQL Injection vulnerability
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
await db.query(query)
typescript
// DANGEROUS - SQL Injection vulnerability
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
await db.query(query)

✅ ALWAYS Use Parameterized Queries

✅ 始终使用参数化查询

typescript
// 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]
)
typescript
// 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]
)

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查询已正确清理

4. Authentication & Authorization

4. 身份验证与授权

JWT Token Handling

JWT令牌处理

typescript
// ❌ 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`)
typescript
// ❌ 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`)

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) {
  // 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 } })
}

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
-- 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);

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

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'

// 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 }} />
}

Content Security Policy

内容安全策略

typescript
// 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()
  }
]
typescript
// 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已清理
  • 已配置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 }
    )
  }

  // Process request
}

SameSite Cookies

SameSite Cookie

typescript
res.setHeader('Set-Cookie',
  `session=${sessionId}; HttpOnly; Secure; SameSite=Strict`)
typescript
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速率限制

typescript
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)
typescript
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)

Expensive Operations

高开销操作限制

typescript
// 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)
typescript
// 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)

Verification Steps

验证步骤

  • Rate limiting on all API endpoints
  • Stricter limits on expensive operations
  • IP-based rate limiting
  • User-based rate limiting (authenticated)
  • 所有API端点均已启用速率限制
  • 高开销操作已设置更严格的限制
  • 已启用基于IP的速率限制
  • 已启用基于用户的速率限制(已认证用户)

8. Sensitive Data Exposure

8. 敏感数据泄露防护

Logging

日志记录

typescript
// ❌ 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 })
typescript
// ❌ 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 })

Error Messages

错误信息

typescript
// ❌ 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 }
  )
}
typescript
// ❌ 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 }
  )
}

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) {
  // 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
}

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

Check for vulnerabilities

npm audit
npm audit

Fix automatically fixable issues

Fix automatically fixable issues

npm audit fix
npm audit fix

Update dependencies

Update dependencies

npm update
npm update

Check for outdated packages

Check for outdated packages

npm outdated
undefined
npm outdated
undefined

Lock Files

锁定文件

bash
undefined
bash
undefined

ALWAYS commit lock files

ALWAYS commit lock files

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

Use in CI/CD for reproducible builds

Use in CI/CD for reproducible builds

npm ci # Instead of npm install
undefined
npm ci # Instead of 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 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)
})

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:用户内容已清理
  • CSRF:已启用防护
  • 身份验证:令牌处理正确
  • 授权:已设置角色检查
  • 速率限制:所有端点均已启用
  • 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.

注意:安全并非可选项。一个漏洞可能会危及整个平台。如有疑问,请谨慎处理。