security-express

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
<overview>
Security audit patterns for Express.js applications covering essential security middleware, CORS configuration, auth patterns, and common vulnerabilities.
</overview> <rules>
<overview>
适用于Express.js应用的安全审计模式,覆盖核心安全中间件、CORS配置、认证模式和常见漏洞。
</overview> <rules>

Essential Security Middleware

核心安全中间件

Helmet.js (Security Headers)

Helmet.js(安全响应头)

javascript
// ❌ Missing security headers
const app = express();

// ✓ Use Helmet
const helmet = require('helmet');
app.use(helmet());
Check if Helmet is installed and used. It sets:
  • Content-Security-Policy
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Strict-Transport-Security
  • And more...
javascript
// ❌ Missing security headers
const app = express();

// ✓ Use Helmet
const helmet = require('helmet');
app.use(helmet());
检查是否已安装并使用Helmet。 它会设置:
  • Content-Security-Policy
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Strict-Transport-Security
  • 其他更多安全头...

Disable X-Powered-By

禁用X-Powered-By

javascript
// ❌ Default (header reveals framework)
const app = express();

// ✓ Disable fingerprinting
app.disable('x-powered-by');
// or: app.set('x-powered-by', false);
javascript
// ❌ 默认配置(响应头会暴露框架信息)
const app = express();

// ✓ 禁用指纹识别
app.disable('x-powered-by');
// or: app.set('x-powered-by', false);

CORS Configuration

CORS配置

javascript
// ❌ CRITICAL: Allow all origins
app.use(cors());
app.use(cors({ origin: '*' }));

// ❌ HIGH: Reflect origin with credentials
app.use(cors({ 
  origin: true,  // Reflects any origin!
  credentials: true 
}));

// ✓ Explicit allowlist
app.use(cors({
  origin: ['https://app.example.com', 'https://admin.example.com'],
  credentials: true,
}));

// ✓ Function for dynamic validation
app.use(cors({
  origin: (origin, callback) => {
    const allowed = ['https://app.example.com'];
    if (!origin || allowed.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
}));
javascript
// ❌ 严重风险:允许所有来源访问
app.use(cors());
app.use(cors({ origin: '*' }));

// ❌ 高风险:反射任意来源并允许凭证
app.use(cors({ 
  origin: true,  // 会反射所有请求来源!
  credentials: true 
}));

// ✓ 显式白名单配置
app.use(cors({
  origin: ['https://app.example.com', 'https://admin.example.com'],
  credentials: true,
}));

// ✓ 动态校验函数配置
app.use(cors({
  origin: (origin, callback) => {
    const allowed = ['https://app.example.com'];
    if (!origin || allowed.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
}));

Body Parser Limits

Body Parser请求大小限制

javascript
// ❌ No limit (DoS risk)
app.use(express.json());

// ✓ Set reasonable limits
app.use(express.json({ limit: '100kb' }));
app.use(express.urlencoded({ extended: true, limit: '100kb' }));
</rules> <vulnerabilities>
javascript
// ❌ 未设置大小限制(存在DoS攻击风险)
app.use(express.json());

// ✓ 设置合理的大小限制
app.use(express.json({ limit: '100kb' }));
app.use(express.urlencoded({ extended: true, limit: '100kb' }));
</rules> <vulnerabilities>

Auth Middleware Patterns

认证中间件模式

Missing Auth on Routes

路由缺失认证

javascript
// ❌ No auth on admin routes
app.get('/api/admin/users', async (req, res) => {
  res.json(await User.find());
});

// ✓ Auth middleware applied
app.get('/api/admin/users', requireAuth, requireAdmin, async (req, res) => {
  res.json(await User.find());
});
javascript
// ❌ 管理后台路由未加认证
app.get('/api/admin/users', async (req, res) => {
  res.json(await User.find());
});

// ✓ 已应用认证中间件
app.get('/api/admin/users', requireAuth, requireAdmin, async (req, res) => {
  res.json(await User.find());
});

Middleware Order Matters

中间件顺序影响安全

javascript
// ❌ Wrong order - static files before auth
app.use(express.static('uploads')); // Exposed!
app.use(requireAuth);

// ✓ Auth before protected static files
app.use('/public', express.static('public')); // Intentionally public
app.use(requireAuth);
app.use('/uploads', express.static('uploads')); // Now protected
javascript
// ❌ 顺序错误:静态资源配置在认证之前
app.use(express.static('uploads')); // 资源完全暴露!
app.use(requireAuth);

// ✓ 认证配置在受保护静态资源之前
app.use('/public', express.static('public')); // 公开资源无需认证
app.use(requireAuth);
app.use('/uploads', express.static('uploads')); // 现在已受保护

Router-Level Auth Gaps

路由级别的认证缺口

javascript
// Check: Is auth applied to all routes in admin router?
const adminRouter = express.Router();
adminRouter.use(requireAuth); // Applied to all routes below
adminRouter.get('/users', getUsers);
adminRouter.delete('/users/:id', deleteUser);

// ❌ Watch for routes defined BEFORE the middleware
const apiRouter = express.Router();
apiRouter.get('/health', getHealth); // No auth (intentional?)
apiRouter.use(requireAuth);
apiRouter.get('/users', getUsers); // Has auth
javascript
// 检查:管理后台路由的所有接口是否都应用了认证?
const adminRouter = express.Router();
adminRouter.use(requireAuth); // 应用到下方所有路由
adminRouter.get('/users', getUsers);
adminRouter.delete('/users/:id', deleteUser);

// ❌ 注意在中间件之前定义的路由
const apiRouter = express.Router();
apiRouter.get('/health', getHealth); // 无认证(是否是有意配置?)
apiRouter.use(requireAuth);
apiRouter.get('/users', getUsers); // 有认证

Common Vulnerabilities

常见漏洞

SQL/NoSQL Injection

SQL/NoSQL注入

javascript
// ❌ String interpolation
const user = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);

// ✓ Parameterized query
const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);

// ❌ MongoDB injection
const user = await User.findOne({ email: req.body.email }); // If email is { $gt: "" }

// ✓ Validate input type
if (typeof req.body.email !== 'string') return res.status(400).json({ error: 'Invalid email' });
javascript
// ❌ 字符串拼接查询
const user = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);

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

// ❌ MongoDB注入风险
const user = await User.findOne({ email: req.body.email }); // 如果传入的email是 { $gt: "" }

// ✓ 校验输入类型
if (typeof req.body.email !== 'string') return res.status(400).json({ error: 'Invalid email' });

Path Traversal

路径遍历

javascript
// ❌ User-controlled path
app.get('/files/:filename', (req, res) => {
  res.sendFile(`./uploads/${req.params.filename}`); // ../../etc/passwd
});

// ✓ Validate and normalize
const path = require('path');
app.get('/files/:filename', (req, res) => {
  const filename = path.basename(req.params.filename);
  const filepath = path.join(__dirname, 'uploads', filename);
  if (!filepath.startsWith(path.join(__dirname, 'uploads'))) {
    return res.status(400).json({ error: 'Invalid path' });
  }
  res.sendFile(filepath);
});
javascript
// ❌ 路径由用户可控
app.get('/files/:filename', (req, res) => {
  res.sendFile(`./uploads/${req.params.filename}`); // 可能被构造为 ../../etc/passwd
});

// ✓ 校验并规范化路径
const path = require('path');
app.get('/files/:filename', (req, res) => {
  const filename = path.basename(req.params.filename);
  const filepath = path.join(__dirname, 'uploads', filename);
  if (!filepath.startsWith(path.join(__dirname, 'uploads'))) {
    return res.status(400).json({ error: 'Invalid path' });
  }
  res.sendFile(filepath);
});

Error Handling

错误处理

javascript
// ❌ Stack traces in production
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.stack }); // Leaks internals
});

// ✓ Safe error handler
app.use((err, req, res, next) => {
  console.error(err); // Log for debugging
  res.status(500).json({ error: 'Internal server error' });
});
javascript
// ❌ 生产环境返回栈追踪信息
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.stack }); // 泄露内部实现细节
});

// ✓ 安全的错误处理逻辑
app.use((err, req, res, next) => {
  console.error(err); // 日志记录用于调试
  res.status(500).json({ error: 'Internal server error' });
});

Session Security

会话安全

javascript
// ❌ Insecure session config
app.use(session({
  secret: 'keyboard cat', // Hardcoded!
  cookie: { secure: false }, // No HTTPS requirement
}));

// ✓ Secure config
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true, // HTTPS only
    httpOnly: true, // No JS access
    sameSite: 'strict', // CSRF protection
    maxAge: 1000 * 60 * 60 * 24, // 24 hours
  },
}));
javascript
// ❌ 不安全的会话配置
app.use(session({
  secret: 'keyboard cat', // 硬编码密钥!
  cookie: { secure: false }, // 不要求HTTPS传输
}));

// ✓ 安全的会话配置
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true, // 仅通过HTTPS传输
    httpOnly: true, // 禁止JS读取
    sameSite: 'strict', // CSRF防护
    maxAge: 1000 * 60 * 60 * 24, // 24小时有效期
  },
}));

Rate Limiting

限流配置

javascript
// Check for rate limiting on auth routes
const rateLimit = require('express-rate-limit');

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 attempts
  message: 'Too many login attempts',
});

app.post('/api/login', authLimiter, loginHandler);
app.post('/api/register', authLimiter, registerHandler);
app.post('/api/forgot-password', authLimiter, forgotPasswordHandler);
</vulnerabilities> <commands>
javascript
// 检查认证接口是否配置了限流
const rateLimit = require('express-rate-limit');

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟窗口
  max: 5, // 最多5次请求
  message: 'Too many login attempts',
});

app.post('/api/login', authLimiter, loginHandler);
app.post('/api/register', authLimiter, registerHandler);
app.post('/api/forgot-password', authLimiter, forgotPasswordHandler);
</vulnerabilities> <commands>

Quick Audit Commands

快速审计命令

bash
undefined
bash
undefined

Check if Helmet is used

检查是否使用了Helmet

rg -n 'helmet\(' . -g ".js" -g ".ts"
rg -n 'helmet\(' . -g ".js" -g ".ts"

Check if x-powered-by is disabled

检查是否禁用了x-powered-by

rg -n "x-powered-by" . -g ".js" -g ".ts"

```bash
rg -n "x-powered-by" . -g ".js" -g ".ts"

```bash

Check for helmet

检查Helmet依赖

rg "helmet" package.json rg "require\(['"]helmet" . rg "from ['"]helmet" .
rg "helmet" package.json rg "require\(['"]helmet" . rg "from ['"]helmet" .

Find CORS config

查找CORS配置

rg "cors\(" . -g ".js" -g ".ts" -A 5
rg "cors\(" . -g ".js" -g ".ts" -A 5

Find routes without auth middleware

查找未加认证中间件的路由

rg "app\.(get|post|put|delete|patch)\(" . -A 1 | grep -v "require.*[Aa]uth"
rg "app\.(get|post|put|delete|patch)\(" . -A 1 | grep -v "require.*[Aa]uth"

Find string interpolation in queries

查找查询语句中的字符串拼接

rg "(query|find|findOne|exec).\`" . -g ".js" -g "*.ts"
rg "(query|find|findOne|exec).\`" . -g ".js" -g "*.ts"

Check session config

检查会话配置

rg "session\(" . -A 10

</commands>

<checklist>
rg "session\(" . -A 10

</commands>

<checklist>

Hardening Checklist

安全加固检查清单

  • Helmet.js installed and used
  • CORS restricted to specific origins
  • Body parser has size limits
  • Auth middleware on all protected routes
  • Rate limiting on auth endpoints
  • Session cookies: secure, httpOnly, sameSite
  • No hardcoded secrets
  • Error handler doesn't leak stack traces
  • Input validation on all user input
  • Parameterized queries (no string concat)
</checklist>
  • Helmet.js已安装并使用
  • CORS仅允许指定源地址访问
  • Body parser已设置请求大小限制
  • 所有受保护路由都已添加认证中间件
  • 认证接口已配置限流
  • 会话Cookie配置了secure、httpOnly、sameSite属性
  • 无硬编码密钥
  • 错误处理逻辑不会泄露栈追踪信息
  • 所有用户输入都做了校验
  • 使用参数化查询(无字符串拼接SQL)
</checklist>