跳转到内容

安全审查

来源: everything-claude-code 技能库

security-review 确保所有代码遵循安全最佳实践并识别潜在漏洞。

  • 实现身份验证或授权
  • 处理用户输入或文件上传
  • 创建新的 API 端点
  • 使用密钥或凭据
  • 实现支付功能
  • 存储或传输敏感数据
  • 集成第三方 API
const apiKey = "sk-proj-xxxxx" // 硬编码的密钥
const dbPassword = "password123" // 源代码中的密码
const apiKey = process.env.OPENAI_API_KEY
const dbUrl = process.env.DATABASE_URL
// 验证密钥存在
if (!apiKey) {
throw new Error('OPENAI_API_KEY not configured')
}
  • 没有硬编码的 API 密钥、令牌或密码
  • 所有密钥在环境变量中
  • .env.local 在 .gitignore 中
  • git 历史中没有密钥
  • 生产密钥在托管平台(Vercel, Railway)
// SQL 注入
const query = `SELECT * FROM users WHERE id = ${userId}`;
// XSS
const html = `<div>${userInput}</div>`;
// 命令注入
exec(`ls ${userPath}`);
// 参数化查询
const query = 'SELECT * FROM users WHERE id = $1';
const result = await db.query(query, [userId]);
// 转义输出
const escaped = escapeHtml(userInput);
const html = `<div>${escaped}</div>`;
// 验证输入
const userId = parseInt(params.id);
if (isNaN(userId)) {
throw new Error('Invalid user ID');
}
// 白名单验证
const allowedTypes = ['jpg', 'png', 'gif'];
const ext = path.extname(file.name).toLowerCase().slice(1);
if (!allowedTypes.includes(ext)) {
throw new Error('Invalid file type');
}
// 好:使用 bcrypt
import bcrypt from 'bcrypt';
async function hashPassword(password: string): Promise<string> {
const salt = await bcrypt.genSalt(12);
return bcrypt.hash(password, salt);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
import jwt from 'jsonwebtoken';
// 好:使用强密钥
const secret = process.env.JWT_SECRET!;
if (secret.length < 32) {
throw new Error('JWT secret too short');
}
// 好:设置过期时间
const token = jwt.sign(
{ userId: user.id },
secret,
{ expiresIn: '1h' } // 短期令牌
);
// 好:存储在 httpOnly cookie
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000 // 1小时
});
// 好:安全会话配置
const session = require('express-session')({
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // 防止 XSS
maxAge: 24 * 60 * 60 * 1000, // 24小时
sameSite: 'strict'
}
});
enum Role {
USER = 'user',
ADMIN = 'admin',
MODERATOR = 'moderator'
}
function checkPermission(requiredRole: Role) {
return (req: Request, res: Response, next: NextFunction) => {
const userRole = req.user?.role;
if (!userRole) {
return res.status(401).json({ error: 'Unauthorized' });
}
const roleHierarchy = [Role.USER, Role.MODERATOR, Role.ADMIN];
const userLevel = roleHierarchy.indexOf(userRole);
const requiredLevel = roleHierarchy.indexOf(requiredRole);
if (userLevel < requiredLevel) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// 使用
router.delete('/users/:id', checkPermission(Role.ADMIN), deleteUser);
async function checkOwnership(
userId: string,
resourceOwnerId: string
): Promise<boolean> {
const user = await getUser(userId);
return user.role === 'admin' || resourceOwnerId === userId;
}
import rateLimit from 'express-rate-limit';
// 好:API 速率限制
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每个 IP 100 次请求
message: 'Too many requests, please try again later'
});
// 更严格的限制
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // 登录/注册每 15 分钟 5 次
skipSuccessfulRequests: true
});
import cors from 'cors';
// 好:限制来源
app.use(cors({
origin: ['https://example.com'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
import { z } from 'zod';
// 好:使用 Zod 验证
const UserSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(100),
age: z.number().int().min(0).max(150).optional()
});
function validateUserInput(data: unknown) {
return UserSchema.parse(data);
}
import crypto from 'crypto';
const algorithm = 'aes-256-gcm';
function encrypt(text: string, key: string): string {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, Buffer.from(key, 'hex'), iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}
function decrypt(encryptedText: string, key: string): string {
const [ivHex, authTagHex, encrypted] = encryptedText.split(':');
const decipher = crypto.createDecipheriv(
algorithm,
Buffer.from(key, 'hex'),
Buffer.from(ivHex, 'hex')
);
decipher.setAuthTag(Buffer.from(authTagHex, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// 好:记录安全事件
logger.info('User login', {
userId: user.id,
ip: req.ip,
success: true,
timestamp: new Date().toISOString()
});
// 不好:记录敏感数据
logger.info('User data', { user, password: 'secret' }); // 永远不要!
✅ 正确做法:
1. 验证输入格式
2. 参数化查询
3. 转义输出
4. 白名单验证
❌ 错误做法:
1. 直接拼接 SQL
2. 直接插入 HTML
3. 信任用户输入
✅ 正确做法:
1. 使用 HTTPS
2. 实现速率限制
3. 验证所有输入
4. 正确的 CORS
5. 使用 httpOnly cookies
❌ 错误做法:
1. 允许所有来源
2. 无速率限制
3. 信任客户端
✅ 正确做法:
1. 环境变量存储
2. 定期轮换密钥
3. 使用密钥托管服务
❌ 错误做法:
1. 硬编码密钥
2. 提交到 git
3. 使用弱密钥
Terminal window
# 安全扫描
npm audit
yarn audit
pip audit
# 依赖检查
npx snyk test
npm outdated
# 代码检查
eslint --ext .ts --rule 'no-console: error'

硬编码密钥

错误:密钥写在代码里 正确:使用环境变量

SQL 注入

错误:字符串拼接 SQL 正确:参数化查询

XSS

错误:直接输出用户输入 正确:转义或使用框架保护

弱密码

错误:明文存储密码 正确:bcrypt 哈希

何时使用

  • 身份验证实现
  • 用户输入处理
  • API 端点创建
  • 敏感数据处理

何时不用

  • 公开静态页面
  • 内部工具
  • 原型开发

关键要点

  • 验证一切
  • 最小权限
  • 加密敏感数据
  • 安全日志

常见错误

  • 硬编码密钥
  • SQL 注入
  • XSS
  • 弱密码
排名风险
1访问控制失效
2加密失败
3注入
4不安全设计
5安全配置错误
6易受攻击和过时的组件
7识别和身份验证失败
8软件和数据完整性失败
9安全日志和监控失败
10服务器端请求伪造
技能关系
code-review代码审查
api-designAPI 设计
database-migrations数据库迁移
  1. 审计现有依赖 - npm audit
  2. 配置 ESLint - 添加安全规则
  3. 环境变量 - 所有密钥使用 .env
  4. 输入验证 - 使用 Zod/joi
  5. HTTPS - 强制使用

官方原文: security-review SKILL.md


💡 提示:安全不是事后补救,而是开发时就需要考虑的事。