بنقرة واحدة
security-review
在添加认证、处理用户输入、使用密钥、创建API端点或实现支付/敏感功能时使用此技能。提供全面的安全检查清单和模式。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
在添加认证、处理用户输入、使用密钥、创建API端点或实现支付/敏感功能时使用此技能。提供全面的安全检查清单和模式。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
REST API 设计模式,包括资源命名、状态码、分页、过滤、错误响应、版本控制和生产 API 的速率限制。
后端架构模式、API 设计、数据库优化和 Node.js、Express、Next.js API 路由的服务器端最佳实践。
通用编码标准、最佳实践和模式,适用于 TypeScript、JavaScript、React 和 Node.js 开发。
Everything Claude Code 的交互式安装向导——引导用户选择和安装技能和规则到用户级或项目级目录,验证路径,并可选地优化已安装的文件。
自动从 Claude Code 会话中提取可复用模式,并保存为学习技能供将来使用。
基于 instinct 的学习系统,通过 hooks 观察会话,创建带置信度评分的原子级 instincts,并将它们进化为技能/命令/代理。
| name | security-review |
| description | 在添加认证、处理用户输入、使用密钥、创建API端点或实现支付/敏感功能时使用此技能。提供全面的安全检查清单和模式。 |
此技能确保所有代码遵循安全最佳实践,并识别潜在漏洞。
const apiKey = "sk-proj-xxxxx" // 硬编码的密钥——危险!
const dbPassword = "password123" // 密码写在源代码里——会被泄露到 Git
为什么危险? 这些密钥会被提交到 Git 仓库,任何能访问代码的人都能看到。
// 从环境变量读取密钥
const apiKey = process.env.OPENAI_API_KEY
const dbUrl = process.env.DATABASE_URL
// 验证密钥是否存在(避免运行时崩溃)
if (!apiKey) {
throw new Error('OPENAI_API_KEY not configured')
}
为什么安全? 密钥存储在环境变量中,不会被提交到代码仓库。
.env.local 在 .gitignore 中(防止本地密钥被提交)git log --all --full-history --source -- "*key*" 检查)import { z } from 'zod'
// 定义验证模式(声明式规则)
const CreateUserSchema = z.object({
email: z.string().email(), // 必须是有效的邮箱格式
name: z.string().min(1).max(100), // 1-100 个字符
age: z.number().int().min(0).max(150) // 0-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
}
}
为什么使用 Zod? 它提供类型安全的验证,防止无效数据进入你的系统。
function validateFileUpload(file: File) {
// 检查大小(5MB 限制)
const maxSize = 5 * 1024 * 1024
if (file.size > maxSize) {
throw new Error('文件过大(最大 5MB)')
}
// 检查类型(只允许图片)
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
if (!allowedTypes.includes(file.type)) {
throw new Error('无效的文件类型')
}
// 检查扩展名(双重验证)
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif']
const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0]
if (!extension || !allowedExtensions.includes(extension)) {
throw new Error('无效的文件扩展名')
}
return true
}
为什么要检查三件事? 攻击者可以伪造文件类型或扩展名,多重验证更安全。
// 危险——SQL 注入漏洞!
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
await db.query(query)
// 如果 userEmail 是:' OR '1'='1
// 生成的查询:SELECT * FROM users WHERE email = '' OR '1'='1'
// 结果:返回所有用户数据!
// 安全——参数化查询
const { data } = await supabase
.from('users')
.select('*')
.eq('email', userEmail) // Supabase 会自动转义
// 或使用原始 SQL
await db.query(
'SELECT * FROM users WHERE email = $1',
[userEmail] // 参数会被安全地转义
)
为什么安全? 参数化查询将数据和代码分开,攻击者无法注入恶意 SQL。
// ❌ 错误:使用 localStorage(易受 XSS 攻击)
localStorage.setItem('token', token)
// ✅ 正确:使用 httpOnly cookies
res.setHeader('Set-Cookie',
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
为什么使用 httpOnly cookies? JavaScript 无法访问 httpOnly cookies,即使有 XSS 攻击也无法窃取令牌。
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 } // 403 Forbidden(禁止访问)
)
}
// 继续删除操作
await db.users.delete({ where: { id: userId } })
}
为什么要检查权限? 普通用户不应该能删除其他用户的数据。
-- 在所有表上启用 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);
什么是 RLS? 数据库层面的权限控制——即使代码有漏洞,数据库也会拒绝未授权访问。
import DOMPurify from 'isomorphic-dompurify'
// 始终清理用户提供的 HTML
function renderUserContent(html: string) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'], // 只允许安全的标签
ALLOWED_ATTR: [] // 不允许任何属性
})
return <div dangerouslySetInnerHTML={{ __html: clean }} />
}
为什么要清理 HTML? 恶意用户可能注入 <script>alert('XSS')</script> 窃取数据。
// 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()
}
]
什么是 CSP? 浏览器安全机制,告诉浏览器只加载来自可信来源的资源。
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 }
)
}
// 处理请求
}
什么是 CSRF 令牌? 随机生成的值,证明请求来自你的网站而不是恶意网站。
res.setHeader('Set-Cookie',
`session=${sessionId}; HttpOnly; Secure; SameSite=Strict`)
SameSite=Strict 意味着什么? Cookie 只在同站请求中发送,防止跨站请求。
import rateLimit from 'express-rate-limit'
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 每个窗口最多 100 次请求
message: '请求过多,请稍后再试'
})
// 应用到路由
app.use('/api/', limiter)
为什么需要速率限制? 防止攻击者每秒发送 1000 次请求尝试破解密码。
// 搜索功能使用更严格的限制
const searchLimiter = rateLimit({
windowMs: 60 * 1000, // 1 分钟
max: 10, // 每分钟最多 10 次请求
message: '搜索请求过多'
})
app.use('/api/search', searchLimiter)
为什么要区分? 搜索操作消耗大量资源,需要更严格的限制。
// ❌ 错误:记录敏感数据
console.log('User login:', { email, password })
console.log('Payment:', { cardNumber, cvv })
// ✅ 正确:编辑敏感数据
console.log('User login:', { email, userId }) // 不记录密码
console.log('Payment:', { last4: card.last4, userId }) // 只记录后四位
为什么危险? 日志文件可能被泄露,不应该包含敏感信息。
// ❌ 错误:暴露内部细节
catch (error) {
return NextResponse.json(
{ error: error.message, stack: error.stack },
{ status: 500 }
)
}
// ✅ 正确:通用错误消息
catch (error) {
console.error('Internal error:', error) // 详细错误记录到服务器日志
return NextResponse.json(
{ error: '发生错误,请稍后再试。' }, // 用户看到的是通用消息
{ status: 500 }
)
}
为什么要隐藏错误细节? 攻击者可以利用错误信息了解系统结构。
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
}
}
为什么要验证签名? 证明用户确实拥有该钱包的私钥。
async function verifyTransaction(transaction: Transaction) {
// 验证收款人
if (transaction.to !== expectedRecipient) {
throw new Error('无效的收款人')
}
// 验证金额
if (transaction.amount > maxAmount) {
throw new Error('金额超过限制')
}
// 验证用户有足够余额
const balance = await getBalance(transaction.from)
if (balance < transaction.amount) {
throw new Error('余额不足')
}
return true
}
为什么要多重验证? 防止钓鱼攻击和恶意交易。
# 检查漏洞
npm audit
# 自动修复可修复的问题
npm audit fix
# 更新依赖
npm update
# 检查过期的包
npm outdated
为什么要更新? 旧版本可能有已知的安全漏洞。
# 始终提交锁定文件
git add package-lock.json
# 在 CI/CD 中使用(可重现构建)
npm ci # 而不是 npm install
什么是锁定文件? 记录确切的依赖版本,确保所有人使用相同版本。
// 测试认证
test('需要认证', async () => {
const response = await fetch('/api/protected')
expect(response.status).toBe(401) // 未认证应该返回 401
})
// 测试授权
test('需要管理员角色', async () => {
const response = await fetch('/api/admin', {
headers: { Authorization: `Bearer ${userToken}` }
})
expect(response.status).toBe(403) // 普通用户应该返回 403
})
// 测试输入验证
test('拒绝无效输入', async () => {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify({ email: 'not-an-email' })
})
expect(response.status).toBe(400) // 无效输入应该返回 400
})
// 测试速率限制
test('强制执行速率限制', 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) // 应该有 429 响应
})
任何生产部署前:
记住:安全不是可选项。一个漏洞可能危及整个平台。有疑问时,宁可谨慎。