硬编码密钥
错误:密钥写在代码里 正确:使用环境变量
来源: everything-claude-code 技能库
security-review 确保所有代码遵循安全最佳实践并识别潜在漏洞。
const apiKey = "sk-proj-xxxxx" // 硬编码的密钥const dbPassword = "password123" // 源代码中的密码const apiKey = process.env.OPENAI_API_KEYconst dbUrl = process.env.DATABASE_URL
// 验证密钥存在if (!apiKey) { throw new Error('OPENAI_API_KEY not configured')}.env.local 在 .gitignore 中// SQL 注入const query = `SELECT * FROM users WHERE id = ${userId}`;
// XSSconst 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');}// 好:使用 bcryptimport 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 cookieres.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. 直接拼接 SQL2. 直接插入 HTML3. 信任用户输入✅ 正确做法:1. 使用 HTTPS2. 实现速率限制3. 验证所有输入4. 正确的 CORS5. 使用 httpOnly cookies
❌ 错误做法:1. 允许所有来源2. 无速率限制3. 信任客户端✅ 正确做法:1. 环境变量存储2. 定期轮换密钥3. 使用密钥托管服务
❌ 错误做法:1. 硬编码密钥2. 提交到 git3. 使用弱密钥# 安全扫描npm audityarn auditpip audit
# 依赖检查npx snyk testnpm outdated
# 代码检查eslint --ext .ts --rule 'no-console: error'硬编码密钥
错误:密钥写在代码里 正确:使用环境变量
SQL 注入
错误:字符串拼接 SQL 正确:参数化查询
XSS
错误:直接输出用户输入 正确:转义或使用框架保护
弱密码
错误:明文存储密码 正确:bcrypt 哈希
何时使用
何时不用
关键要点
常见错误
| 排名 | 风险 |
|---|---|
| 1 | 访问控制失效 |
| 2 | 加密失败 |
| 3 | 注入 |
| 4 | 不安全设计 |
| 5 | 安全配置错误 |
| 6 | 易受攻击和过时的组件 |
| 7 | 识别和身份验证失败 |
| 8 | 软件和数据完整性失败 |
| 9 | 安全日志和监控失败 |
| 10 | 服务器端请求伪造 |
| 技能 | 关系 |
|---|---|
| code-review | 代码审查 |
| api-design | API 设计 |
| database-migrations | 数据库迁移 |
npm audit官方原文: security-review SKILL.md
💡 提示:安全不是事后补救,而是开发时就需要考虑的事。