mcpField
S
security-express

security-express

github.com/igorwarzocha安全122150 次安装38 周前更新
代码仓库

关于 security-express

security-express 是面向 Express.js 应用的安全审计技能,整理中间件、身份验证和常见漏洞的检查模式。适用于审查和加固 Express.js 项目的开发者。

主要功能

  1. 01检查 Helmet 安全响应头
  2. 02审查 CORS 与请求体大小限制
  3. 03核查路由身份验证和中间件顺序
  4. 04识别注入、路径遍历与错误信息泄露
  5. 05检查会话安全和登录限速

适用场景

  1. 01审计 Express.js 项目的安全配置
  2. 02检查管理路由是否缺少身份验证
  3. 03查找用户输入处理中的常见漏洞

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/igorwarzocha/opencode-workflows --skill security-express

安装后在 Claude Code 中输入/security-express确认已加载。

附带文件scripts/scan.sh

详细介绍

针对 Express.js 应用的安全审计模式,涵盖必要的安全中间件、CORS 配置、身份验证模式和常见漏洞。

必要的安全中间件

Helmet.js(安全标头)
// ❌ 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
  • 等等……
禁用 X-Powered-By
// ❌ Default (header reveals framework)
const app = express();

// ✓ Disable fingerprinting
app.disable('x-powered-by');
// or: app.set('x-powered-by', false);
CORS 配置
// ❌ 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,
}));

请求体解析器限制

// ❌ 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' }));

身份验证中间件模式

路由缺少身份验证
// ❌ 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());
});
中间件顺序很重要
// ❌ 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
路由器级别的身份验证漏洞
// 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

常见漏洞

SQL/NoSQL 注入
// ❌ 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' });
路径遍历
// ❌ 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);
});
错误处理
// ❌ 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' });
});
会话安全
// ❌ 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
  },
}));

速率限制

// 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);

快速审计命令

# Check if Helmet is used
rg -n 'helmet\\(' . -g "*.js" -g "*.ts"

# Check if x-powered-by is disabled
rg -n "x-powered-by" . -g "*.js" -g "*.ts"
# Check for helmet
rg "helmet" package.json
rg "require\\(['\"]helmet" .
rg "from ['\"]helmet" .

# Find CORS config
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"

# Find string interpolation in queries
rg "(query|find|findOne|exec).*\\`" . -g "*.js" -g "*.ts"

# Check session config
rg "session\\(" . -A 10

安全加固检查清单

  • 已安装并使用 Helmet.js
  • CORS 限制为特定来源
  • 请求体解析器设置了大小限制
  • 所有受保护的路由均使用身份验证中间件
  • 身份验证端点已设置速率限制
  • 会话 Cookie:secure、httpOnly、sameSite
  • 没有硬编码的密钥
  • 错误处理程序不会泄露堆栈跟踪
  • 对所有用户输入进行验证
  • 使用参数化查询(不拼接字符串)

评论

支持 Markdown