security-and-hardening

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Security and Hardening

安全与加固

Overview

概述

Security-first development practices for web applications. Treat every external input as hostile, every secret as sacred, and every authorization check as mandatory. Security isn't a phase — it's a constraint on every line of code that touches user data, authentication, or external systems.
Web应用的安全优先开发实践:将所有外部输入视为恶意内容,所有密钥视为核心敏感信息,所有权限校验视为强制要求。安全不是某个独立阶段——它是每一行涉及用户数据、身份验证或外部系统的代码都需要遵守的约束。

When to Use

适用场景

  • Building anything that accepts user input
  • Implementing authentication or authorization
  • Storing or transmitting sensitive data
  • Integrating with external APIs or services
  • Adding file uploads, webhooks, or callbacks
  • Handling payment or PII data
  • 构建任何接收用户输入的功能
  • 实现身份验证或授权逻辑
  • 存储或传输敏感数据
  • 与外部API或服务集成
  • 新增文件上传、webhook或回调功能
  • 处理支付或个人身份信息(PII)

The Three-Tier Boundary System

三层边界系统

Always Do (No Exceptions)

必须始终执行(无例外)

  • Validate all external input at the system boundary (API routes, form handlers)
  • Parameterize all database queries — never concatenate user input into SQL
  • Encode output to prevent XSS (use framework auto-escaping, don't bypass it)
  • Use HTTPS for all external communication
  • Hash passwords with bcrypt/scrypt/argon2 (never store plaintext)
  • Set security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options)
  • Use httpOnly, secure, sameSite cookies for sessions
  • Run
    npm audit
    (or equivalent) before every release
  • 在系统边界(API路由、表单处理函数)验证所有外部输入
  • 所有数据库查询使用参数化查询——永远不要将用户输入拼接进SQL语句
  • 对输出进行编码以防范XSS(使用框架自带的自动转义能力,不要绕过该机制)
  • 所有外部通信使用HTTPS
  • 使用bcrypt/scrypt/argon2哈希存储密码(永远不要存储明文密码)
  • 配置安全头(CSP、HSTS、X-Frame-Options、X-Content-Type-Options)
  • 会话Cookie使用httpOnly、secure、sameSite属性
  • 每次发布前执行
    npm audit
    (或对应语言/工具的等价检查命令)

Ask First (Requires Human Approval)

需先申请(需要人工审批)

  • Adding new authentication flows or changing auth logic
  • Storing new categories of sensitive data (PII, payment info)
  • Adding new external service integrations
  • Changing CORS configuration
  • Adding file upload handlers
  • Modifying rate limiting or throttling
  • Granting elevated permissions or roles
  • 新增身份验证流程或修改鉴权逻辑
  • 存储新类别的敏感数据(PII、支付信息)
  • 新增外部服务集成
  • 修改CORS配置
  • 新增文件上传处理逻辑
  • 修改限流或流量削峰规则
  • 授予高权限角色或权限

Never Do

绝对禁止

  • Never commit secrets to version control (API keys, passwords, tokens)
  • Never log sensitive data (passwords, tokens, full credit card numbers)
  • Never trust client-side validation as a security boundary
  • Never disable security headers for convenience
  • Never use
    eval()
    or
    innerHTML
    with user-provided data
  • Never store sessions in client-accessible storage (localStorage for auth tokens)
  • Never expose stack traces or internal error details to users
  • 永远不要向版本控制提交密钥(API密钥、密码、令牌)
  • 永远不要记录敏感数据(密码、令牌、完整信用卡号)
  • 永远不要将客户端校验作为安全边界
  • 永远不要为了便利禁用安全头
  • 永远不要对用户提供的数据使用
    eval()
    innerHTML
  • 永远不要将会话存储在客户端可访问的存储中(比如用localStorage存储鉴权令牌)
  • 永远不要向用户暴露栈追踪或内部错误详情

OWASP Top 10 Prevention

OWASP Top 10 防护

1. Injection (SQL, NoSQL, OS Command)

1. 注入漏洞(SQL、NoSQL、操作系统命令)

typescript
// 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 } });
typescript
// BAD: 字符串拼接导致SQL注入
const query = `SELECT * FROM users WHERE id = '${userId}'`;

// GOOD: 参数化查询
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);

// GOOD: 自带参数化输入能力的ORM
const user = await prisma.user.findUnique({ where: { id: userId } });

2. Broken Authentication

2. 身份验证失效

typescript
// 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,  // From environment, not code
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,     // Not accessible via JavaScript
    secure: true,       // HTTPS only
    sameSite: 'lax',    // CSRF protection
    maxAge: 24 * 60 * 60 * 1000,  // 24 hours
  },
}));
typescript
// 密码哈希
import { hash, compare } from 'bcrypt';

const SALT_ROUNDS = 12;
const hashedPassword = await hash(plaintext, SALT_ROUNDS);
const isValid = await compare(plaintext, hashedPassword);

// 会话管理
app.use(session({
  secret: process.env.SESSION_SECRET,  // 来自环境变量,不硬编码在代码中
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,     // 禁止JavaScript访问
    secure: true,       // 仅在HTTPS下传输
    sameSite: 'lax',    // CSRF防护
    maxAge: 24 * 60 * 60 * 1000,  // 24小时有效期
  },
}));

3. Cross-Site Scripting (XSS)

3. 跨站脚本(XSS)

typescript
// 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);
typescript
// BAD: 将用户输入直接渲染为HTML
element.innerHTML = userInput;

// GOOD: 使用框架自动转义(React默认开启该能力)
return <div>{userInput}</div>;

// 如果必须渲染HTML,先做消毒处理
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput);

4. Broken Access Control

4. 访问控制失效

typescript
// 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 to modify this task' }
    });
  }

  // Proceed with update
  const updated = await taskService.update(req.params.id, req.body);
  return res.json(updated);
});
typescript
// 始终校验权限,而不仅仅是校验是否登录
app.patch('/api/tasks/:id', authenticate, async (req, res) => {
  const task = await taskService.findById(req.params.id);

  // 校验当前登录用户是否拥有该资源的所有权
  if (task.ownerId !== req.user.id) {
    return res.status(403).json({
      error: { code: 'FORBIDDEN', message: '无权限修改该任务' }
    });
  }

  // 继续执行更新逻辑
  const updated = await taskService.update(req.params.id, req.body);
  return res.json(updated);
});

5. Security Misconfiguration

5. 安全配置错误

typescript
// Security headers (use helmet for Express)
import helmet from 'helmet';
app.use(helmet());

// Content Security Policy
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],
    styleSrc: ["'self'", "'unsafe-inline'"],  // Tighten if possible
    imgSrc: ["'self'", 'data:', 'https:'],
    connectSrc: ["'self'"],
  },
}));

// CORS — restrict to known origins
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
  credentials: true,
}));
typescript
// 安全头配置(Express应用可使用helmet)
import helmet from 'helmet';
app.use(helmet());

// 内容安全策略配置
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],
    styleSrc: ["'self'", "'unsafe-inline'"],  // 可收紧的话请进一步限制
    imgSrc: ["'self'", 'data:', 'https:'],
    connectSrc: ["'self'"],
  },
}));

// CORS —— 仅允许已知来源
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
  credentials: true,
}));

6. Sensitive Data Exposure

6. 敏感数据暴露

typescript
// Never return sensitive fields in API responses
function sanitizeUser(user: UserRecord): PublicUser {
  const { passwordHash, resetToken, ...publicFields } = user;
  return publicFields;
}

// Use environment variables for secrets
const API_KEY = process.env.STRIPE_API_KEY;
if (!API_KEY) throw new Error('STRIPE_API_KEY not configured');
typescript
// 永远不要在API响应中返回敏感字段
function sanitizeUser(user: UserRecord): PublicUser {
  const { passwordHash, resetToken, ...publicFields } = user;
  return publicFields;
}

// 使用环境变量存储密钥
const API_KEY = process.env.STRIPE_API_KEY;
if (!API_KEY) throw new Error('STRIPE_API_KEY未配置');

Input Validation Patterns

输入校验模式

Schema Validation at Boundaries

边界处的Schema校验

typescript
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(),
});

// Validate at the route handler
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(),
      },
    });
  }
  // result.data is now typed and validated
  const task = await taskService.create(result.data);
  return res.status(201).json(task);
});
typescript
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: '输入不合法',
        details: result.error.flatten(),
      },
    });
  }
  // result.data 已经完成类型校验和格式校验
  const task = await taskService.create(result.data);
  return res.status(201).json(task);
});

File Upload Safety

文件上传安全

typescript
// Restrict file types and sizes
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
const MAX_SIZE = 5 * 1024 * 1024; // 5MB

function validateUpload(file: UploadedFile) {
  if (!ALLOWED_TYPES.includes(file.mimetype)) {
    throw new ValidationError('File type not allowed');
  }
  if (file.size > MAX_SIZE) {
    throw new ValidationError('File too large (max 5MB)');
  }
  // Don't trust the file extension — check magic bytes if critical
}
typescript
// 限制文件类型和大小
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
const MAX_SIZE = 5 * 1024 * 1024; // 5MB

function validateUpload(file: UploadedFile) {
  if (!ALLOWED_TYPES.includes(file.mimetype)) {
    throw new ValidationError('不允许的文件类型');
  }
  if (file.size > MAX_SIZE) {
    throw new ValidationError('文件过大(最大支持5MB)');
  }
  // 不要信任文件扩展名——如果是高敏感场景请校验文件魔术字节
}

Triaging npm audit Results

npm audit 结果分级处理

Not all audit findings require immediate action. Use this decision tree:
npm audit reports a vulnerability
├── Severity: critical or high
│   ├── Is the vulnerable code reachable in your app?
│   │   ├── YES --> Fix immediately (update, patch, or replace the dependency)
│   │   └── NO (dev-only dep, unused code path) --> Fix soon, but not a blocker
│   └── Is a fix available?
│       ├── YES --> Update to the patched version
│       └── NO --> Check for workarounds, consider replacing the dependency, or add to allowlist with a review date
├── Severity: moderate
│   ├── Reachable in production? --> Fix in the next release cycle
│   └── Dev-only? --> Fix when convenient, track in backlog
└── Severity: low
    └── Track and fix during regular dependency updates
Key questions:
  • Is the vulnerable function actually called in your code path?
  • Is the dependency a runtime dependency or dev-only?
  • Is the vulnerability exploitable given your deployment context (e.g., a server-side vulnerability in a client-only app)?
When you defer a fix, document the reason and set a review date.
不是所有的审计发现都需要立即修复,可参考如下决策树:
npm audit 报告漏洞
├── 严重级别:critical 或 high
│   ├── 漏洞代码在你的应用中是否可达?
│   │   ├── 是 --> 立即修复(更新、打补丁或替换依赖)
│   │   └── 否(仅开发依赖、代码路径未使用) --> 尽快修复,但不阻塞发布
│   └── 是否有可用的修复方案?
│       ├── 是 --> 更新到已修复的版本
│       └── 否 --> 查找替代方案,考虑替换依赖,或加入白名单并设置复查日期
├── 严重级别:moderate
│   ├── 生产环境可达? --> 在下个发布周期修复
│   └── 仅开发依赖? --> 方便的时候修复,加入待办跟踪
└── 严重级别:low
    └── 跟踪并在常规依赖更新时修复
核心判断问题:
  • 漏洞函数在你的代码路径中是否真的会被调用?
  • 该依赖是运行时依赖还是仅开发依赖?
  • 结合你的部署上下文,该漏洞是否可被利用?(比如客户端应用中的服务端漏洞是无法被利用的)
如果你选择推迟修复,请记录原因并设置复查日期。

Rate Limiting

限流

typescript
import rateLimit from 'express-rate-limit';

// General API rate limit
app.use('/api/', rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,                   // 100 requests per window
  standardHeaders: true,
  legacyHeaders: false,
}));

// Stricter limit for auth endpoints
app.use('/api/auth/', rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 10,  // 10 attempts per 15 minutes
}));
typescript
import rateLimit from 'express-rate-limit';

// 通用API限流规则
app.use('/api/', rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100,                   // 每个窗口最多100次请求
  standardHeaders: true,
  legacyHeaders: false,
}));

// 鉴权接口使用更严格的限流规则
app.use('/api/auth/', rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 10,  // 15分钟内最多10次尝试
}));

Secrets Management

密钥管理

.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
Always check before committing:
bash
undefined
.env 文件规范:
  ├── .env.example  → 提交到版本控制(带占位符的模板文件)
  ├── .env          → 不提交(存储真实密钥)
  └── .env.local    → 不提交(本地环境覆盖配置)

.gitignore 必须包含:
  .env
  .env.local
  .env.*.local
  *.pem
  *.key
提交前必须检查:
bash
undefined

Check for accidentally staged secrets

检查是否有不小心暂存的密钥

git diff --cached | grep -i "password|secret|api_key|token"
undefined
git diff --cached | grep -i "password|secret|api_key|token"
undefined

Security Review Checklist

安全审查清单

markdown
undefined
markdown
undefined

Authentication

身份验证

  • Passwords hashed with bcrypt/scrypt/argon2 (salt rounds ≥ 12)
  • Session tokens are httpOnly, secure, sameSite
  • Login has rate limiting
  • Password reset tokens expire
  • 密码使用bcrypt/scrypt/argon2哈希存储(加盐轮数≥12)
  • 会话令牌配置了httpOnly、secure、sameSite属性
  • 登录接口配置了限流
  • 密码重置令牌有过期时间

Authorization

授权

  • Every endpoint checks user permissions
  • Users can only access their own resources
  • Admin actions require admin role verification
  • 每个接口都校验用户权限
  • 用户仅能访问自己拥有的资源
  • 管理员操作需要额外校验管理员角色

Input

输入

  • All user input validated at the boundary
  • SQL queries are parameterized
  • HTML output is encoded/escaped
  • 所有用户输入都在系统边界完成校验
  • SQL查询都使用参数化查询
  • HTML输出做了编码/转义

Data

数据

  • No secrets in code or version control
  • Sensitive fields excluded from API responses
  • PII encrypted at rest (if applicable)
  • 代码和版本控制中没有硬编码密钥
  • API响应中排除了敏感字段
  • 个人身份信息(如需要)在存储时加密

Infrastructure

基础设施

  • Security headers configured (CSP, HSTS, etc.)
  • CORS restricted to known origins
  • Dependencies audited for vulnerabilities
  • Error messages don't expose internals
undefined
  • 配置了安全头(CSP、HSTS等)
  • CORS仅允许已知来源
  • 完成了依赖漏洞审计
  • 错误信息不会暴露内部细节
undefined

Common Rationalizations

常见错误认知

RationalizationReality
"This is an internal tool, security doesn't matter"Internal tools get compromised. Attackers target the weakest link.
"We'll add security later"Security retrofitting is 10x harder than building it in. Add it now.
"No one would try to exploit this"Automated scanners will find it. Security by obscurity is not security.
"The framework handles security"Frameworks provide tools, not guarantees. You still need to use them correctly.
"It's just a prototype"Prototypes become production. Security habits from day one.
错误认知实际情况
"这是内部工具,不需要考虑安全"内部工具也会被攻破,攻击者永远会瞄准最薄弱的环节
"我们后面再加安全机制"事后补安全的成本是内置安全的10倍,现在就加
"没人会来攻击这个功能"自动化扫描器会发现漏洞,靠隐蔽性保障安全不是真正的安全
"框架会处理安全问题"框架只提供工具,不提供安全保障,你还是需要正确使用这些工具
"这只是个原型"原型最终会变成生产环境,安全习惯要从第一天养成

Red Flags

危险信号

  • User input passed directly to database queries, shell commands, or HTML rendering
  • Secrets in source code or commit history
  • API endpoints without authentication or authorization checks
  • Missing CORS configuration or wildcard (
    *
    ) origins
  • No rate limiting on authentication endpoints
  • Stack traces or internal errors exposed to users
  • Dependencies with known critical vulnerabilities
  • 用户输入直接传入数据库查询、shell命令或HTML渲染逻辑
  • 源代码或提交历史中存在密钥
  • API接口没有身份验证或授权校验
  • 缺少CORS配置或使用通配符(
    *
    )来源
  • 鉴权接口没有限流
  • 向用户暴露栈追踪或内部错误
  • 依赖存在已知的严重漏洞

Verification

验证

After implementing security-relevant code:
  • npm audit
    shows no critical or high vulnerabilities
  • No secrets in source code or git history
  • All user input validated at system boundaries
  • Authentication and authorization checked on every protected endpoint
  • Security headers present in response (check with browser DevTools)
  • Error responses don't expose internal details
  • Rate limiting active on auth endpoints
实现安全相关代码后,检查以下项:
  • npm audit
    没有critical或high级别的漏洞
  • 源代码或git历史中没有密钥
  • 所有用户输入都在系统边界完成校验
  • 所有受保护接口都做了身份验证和授权校验
  • 响应中包含安全头(可通过浏览器DevTools检查)
  • 错误响应不会暴露内部细节
  • 鉴权接口已开启限流