security-review
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSecurity 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 codetypescript
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
- in .gitignore
.env.local - No secrets in git history
- Production secrets in hosting platform (Vercel, Railway)
- 代码中无硬编码的API密钥、令牌或密码
- 所有密钥均存储在环境变量中
- 已添加到.gitignore
.env.local - 密钥未出现在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
undefinedbash
undefinedCheck 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
undefinednpm outdated
undefinedLock Files
锁定文件
bash
undefinedbash
undefinedALWAYS 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
undefinednpm ci # Instead of npm install
undefinedVerification 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.
注意:安全并非可选项。一个漏洞可能会危及整个平台。如有疑问,请谨慎处理。