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
❌ 禁止这么做
javascript
const apiKey = "sk-proj-xxxxx" // Hardcoded secret
const dbPassword = "password123" // In source codejavascript
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
- 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'
// 定义校验 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
undefinedbash
undefinedCheck 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
undefinednpm outdated
undefinedLock Files
锁文件
bash
undefinedbash
undefinedALWAYS 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
undefinednpm ci # 代替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('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.
注意:安全不是可选项,一个漏洞就可能危及整个平台。如果不确定,优先选择更安全的方案。