mcpField
S
security-express

security-express

github.com/igorwarzochaセキュリティ122150 回インストール38 週間前に更新
リポジトリ

security-express について

security-expressは、Express.jsアプリケーション向けのセキュリティ監査スキルです。ミドルウェア、認証、一般的な脆弱性の確認パターンを開発者に提供します。

主な機能

  1. 01Helmetのセキュリティヘッダー確認
  2. 02CORSとリクエスト本文サイズの確認
  3. 03ルート認証とミドルウェア順序の確認
  4. 04インジェクション、パストラバーサル、エラー漏えいの検出
  5. 05セッション保護とログイン頻度制限の確認

活用シーン

  1. 01Express.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 に設定されている
  • ハードコードされたシークレットがない
  • エラーハンドラーがスタックトレースを漏えいしない
  • すべてのユーザー入力が検証されている
  • パラメーター化クエリを使用している(文字列連結なし)

コメント