Development
安全与加固
针对漏洞对代码进行加固。在处理用户输入、认证、数据存储或外部集成时使用。
面向 Web 应用的安全优先开发实践。把每一个外部输入都当作敌意输入来对待。
在加固之前,花五分钟像攻击者一样思考:
| 威胁 | 提问 | 典型缓解措施 | |---|---|---| | Spoofing(仿冒) | 是否有人能冒充某个用户/服务? | 认证、签名验证 | | Tampering(篡改) | 数据能否在传输中或静态时被篡改? | 完整性校验、参数化查询 | | Repudiation(抵赖) | 某个操作之后能否被否认? | 对安全事件进行审计日志记录 | | Information disclosure(信息泄露) | 数据是否会泄露? | 加密、字段允许列表 | | Denial of service(拒绝服务) | 是否会被压垮? | 限流、输入大小上限 | | Elevation of privilege(权限提升) | 用户能否获得本不应有的权限? | 授权检查、最小权限原则 |
npm auditeval() 或 innerHTML// BAD: SQL injection via string concatenation
const query = `SELECT * FROM users WHERE id = '${userId}'`;
// GOOD: Parameterized query
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
// GOOD: ORM with parameterized input
const user = await prisma.user.findUnique({ where: { id: userId } });
// Password hashing
import { hash, compare } from 'bcrypt';
const SALT_ROUNDS = 12;
const hashedPassword = await hash(plaintext, SALT_ROUNDS);
const isValid = await compare(plaintext, hashedPassword);
// Session management
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 24 * 60 * 60 * 1000,
},
}));
// BAD: Rendering user input as HTML
element.innerHTML = userInput;
// GOOD: Use framework auto-escaping (React does this by default)
return <div>{userInput}</div>;
// If you MUST render HTML, sanitize first
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput);
// Always check authorization, not just authentication
app.patch('/api/tasks/:id', authenticate, async (req, res) => {
const task = await taskService.findById(req.params.id);
// Check that the authenticated user owns this resource
if (task.ownerId !== req.user.id) {
return res.status(403).json({
error: { code: 'FORBIDDEN', message: 'Not authorized' }
});
}
const updated = await taskService.update(req.params.id, req.body);
return res.json(updated);
});
import { z } from 'zod';
const CreateTaskSchema = z.object({
title: z.string().min(1).max(200).trim(),
description: z.string().max(2000).optional(),
priority: z.enum(['low', 'medium', 'high']).default('medium'),
dueDate: z.string().datetime().optional(),
});
app.post('/api/tasks', async (req, res) => {
const result = CreateTaskSchema.safeParse(req.body);
if (!result.success) {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid input',
details: result.error.flatten(),
},
});
}
const task = await taskService.create(result.data);
return res.status(201).json(task);
});
import rateLimit from 'express-rate-limit';
// General API rate limit
app.use('/api/', rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
standardHeaders: true,
legacyHeaders: false,
}));
// Stricter limit for auth endpoints
app.use('/api/auth/', rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
}));
.env files:
├── .env.example → Committed (template with placeholder values)
├── .env → NOT committed (contains real secrets)
└── .env.local → NOT committed (local overrides)
.gitignore must include:
.env
.env.local
.env.*.local
*.pem
*.key
提交前始终检查:
git diff --cached | grep -i "password\|secret\|api_key\|token"
在实现与安全相关的代码后:
npm audit 未显示严重(critical)或高危(high)漏洞