セキュアコーディング実践ガイド2025 - 最新サイバー脅威への対策とベストプラクティス
2025年の最新サイバー脅威に対応するセキュアコーディングの実践方法を徹底解説。AI悪用攻撃、サプライチェーン攻撃、ゼロトラストセキュリティなど、最新の脅威動向と具体的な対策コードを紹介します。
2025年最新のWebセキュリティ脅威と対策。OWASP Top 10、CSP、SRI、セキュアヘッダーなど実践的なセキュリティ対策を解説します。
2025 年の Web セキュリティ環境は、AI 技術の普及とともに新たな脅威が登場しています。従来の対策に加え、最新の攻撃手法に対応した包括的なセキュリティ戦略が必要です。
// ❌ 脆弱な実装
app.get('/api/users/:id', (req, res) => {
const userId = req.params.id;
// 認可チェックなし
const user = getUserById(userId);
res.json(user);
});
// ✅ セキュアな実装
app.get('/api/users/:id', authenticateToken, (req, res) => {
const requestedUserId = req.params.id;
const currentUserId = req.user.id;
const userRole = req.user.role;
// 自分の情報または管理者のみアクセス可能
if (requestedUserId !== currentUserId && userRole !== 'admin') {
return res.status(403).json({ error: 'Access denied' });
}
const user = getUserById(requestedUserId);
// 機密情報をフィルタリング
const safeUser = {
id: user.id,
name: user.name,
email: userRole === 'admin' ? user.email : undefined
};
res.json(safeUser);
});
// 認可ミドルウェアの実装
function requireRole(roles) {
return (req, res, next) => {
if (!req.user || !roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// 使用例
app.delete('/api/users/:id', authenticateToken, requireRole(['admin']), (req, res) => {
// 管理者のみ実行可能
deleteUser(req.params.id);
res.json({ success: true });
});
// ❌ 脆弱な暗号化
const crypto = require('crypto');
function weakEncrypt(text) {
const cipher = crypto.createCipher('aes192', 'weak-password');
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
// ✅ セキュアな暗号化
const crypto = require('crypto');
class SecureEncryption {
constructor() {
this.algorithm = 'aes-256-gcm';
this.keyLength = 32; // 256 bits
this.ivLength = 16; // 128 bits
this.tagLength = 16; // 128 bits
}
generateKey() {
return crypto.randomBytes(this.keyLength);
}
encrypt(text, key) {
const iv = crypto.randomBytes(this.ivLength);
const cipher = crypto.createCipherGCM(this.algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
tag: tag.toString('hex')
};
}
decrypt(encryptedData, key) {
const { encrypted, iv, tag } = encryptedData;
const decipher = crypto.createDecipherGCM(
this.algorithm,
key,
Buffer.from(iv, 'hex')
);
decipher.setAuthTag(Buffer.from(tag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
// パスワードハッシュ化
const bcrypt = require('bcrypt');
async function hashPassword(password) {
const saltRounds = 12; // 2025年推奨値
return await bcrypt.hash(password, saltRounds);
}
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
-- ❌ SQLインジェクション脆弱性
SELECT * FROM users WHERE username = '${userInput}';
-- ✅ パラメータ化クエリ
SELECT * FROM users WHERE username = ?;
// Node.js での実装例
const mysql = require('mysql2/promise');
class UserRepository {
constructor(connection) {
this.db = connection;
}
// ❌ 脆弱な実装
async getUserByNameUnsafe(username) {
const query = `SELECT * FROM users WHERE username = '${username}'`;
const [rows] = await this.db.execute(query);
return rows[0];
}
// ✅ セキュアな実装
async getUserByName(username) {
const query = 'SELECT id, username, email FROM users WHERE username = ?';
const [rows] = await this.db.execute(query, [username]);
return rows[0];
}
// 複雑なクエリの例
async searchUsers(filters) {
let query = 'SELECT id, username, email FROM users WHERE 1=1';
const params = [];
if (filters.username) {
query += ' AND username LIKE ?';
params.push(`%${filters.username}%`);
}
if (filters.email) {
query += ' AND email = ?';
params.push(filters.email);
}
if (filters.createdAfter) {
query += ' AND created_at > ?';
params.push(filters.createdAfter);
}
query += ' ORDER BY created_at DESC LIMIT ?';
params.push(filters.limit || 50);
const [rows] = await this.db.execute(query, params);
return rows;
}
}
// NoSQLインジェクション対策(MongoDB例)
const { MongoClient } = require('mongodb');
class MongoUserRepository {
constructor(db) {
this.db = db;
this.collection = db.collection('users');
}
// ❌ 脆弱な実装
async getUserUnsafe(userInput) {
// userInput が { $ne: null } のような場合、全ユーザーが返される
return await this.collection.findOne({ username: userInput });
}
// ✅ セキュアな実装
async getUser(username) {
// 入力値の型チェック
if (typeof username !== 'string') {
throw new Error('Username must be a string');
}
return await this.collection.findOne({
username: { $eq: username } // 明示的な等価演算子
});
}
async searchUsers(filters) {
const query = {};
// 各フィルターの型チェックとサニタイズ
if (filters.username && typeof filters.username === 'string') {
query.username = { $regex: filters.username, $options: 'i' };
}
if (filters.email && typeof filters.email === 'string') {
query.email = { $eq: filters.email };
}
if (filters.age && typeof filters.age === 'number') {
query.age = { $gte: filters.age };
}
return await this.collection.find(query).limit(50).toArray();
}
}
// Express.js でのCSP実装
const helmet = require('helmet');
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
"'unsafe-inline'", // 段階的に削除予定
"https://cdn.jsdelivr.net",
"https://unpkg.com"
],
styleSrc: [
"'self'",
"'unsafe-inline'", // 段階的に削除予定
"https://fonts.googleapis.com"
],
fontSrc: [
"'self'",
"https://fonts.gstatic.com"
],
imgSrc: [
"'self'",
"data:",
"https:"
],
connectSrc: [
"'self'",
"https://api.example.com"
],
frameSrc: ["'none'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"]
},
reportOnly: false // 本番環境では false
}));
// CSPレポート収集エンドポイント
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
console.log('CSP Violation:', req.body);
// ログシステムに送信
logger.warn('CSP Violation', {
violation: req.body['csp-report'],
userAgent: req.get('User-Agent'),
ip: req.ip
});
res.status(204).end();
});
// Nonce生成ミドルウェア
const crypto = require('crypto');
function generateNonce(req, res, next) {
res.locals.nonce = crypto.randomBytes(16).toString('base64');
next();
}
app.use(generateNonce);
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
(req, res) => `'nonce-${res.locals.nonce}'`
],
styleSrc: [
"'self'",
(req, res) => `'nonce-${res.locals.nonce}'`
]
}
}));
// テンプレートでの使用例(EJS)
app.get('/', (req, res) => {
res.render('index', { nonce: res.locals.nonce });
});
<!-- テンプレート内 -->
<script nonce="<%= nonce %>">
// インラインスクリプト
console.log('This script is allowed');
</script>
<style nonce="<%= nonce %>">
/* インラインスタイル */
.example { color: red; }
</style>
const helmet = require('helmet');
app.use(helmet({
// Content Security Policy
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'", "https:"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'", "https:", "data:"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
},
// HTTP Strict Transport Security
hsts: {
maxAge: 31536000, // 1年
includeSubDomains: true,
preload: true
},
// X-Frame-Options
frameguard: {
action: 'deny'
},
// X-Content-Type-Options
noSniff: true,
// Referrer Policy
referrerPolicy: {
policy: 'strict-origin-when-cross-origin'
},
// Permissions Policy
permissionsPolicy: {
camera: [],
microphone: [],
geolocation: ['self'],
notifications: ['self']
}
}));
// カスタムセキュリティヘッダー
app.use((req, res, next) => {
// X-XSS-Protection (レガシーブラウザ用)
res.setHeader('X-XSS-Protection', '1; mode=block');
// Cross-Origin-Embedder-Policy
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
// Cross-Origin-Opener-Policy
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
// Cross-Origin-Resource-Policy
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
next();
});
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
class JWTManager {
constructor() {
this.accessTokenSecret = process.env.JWT_ACCESS_SECRET;
this.refreshTokenSecret = process.env.JWT_REFRESH_SECRET;
this.accessTokenExpiry = '15m';
this.refreshTokenExpiry = '7d';
}
generateTokenPair(payload) {
const accessToken = jwt.sign(
payload,
this.accessTokenSecret,
{
expiresIn: this.accessTokenExpiry,
issuer: 'your-app',
audience: 'your-app-users'
}
);
const refreshToken = jwt.sign(
{ userId: payload.userId },
this.refreshTokenSecret,
{
expiresIn: this.refreshTokenExpiry,
issuer: 'your-app',
audience: 'your-app-users'
}
);
return { accessToken, refreshToken };
}
verifyAccessToken(token) {
try {
return jwt.verify(token, this.accessTokenSecret, {
issuer: 'your-app',
audience: 'your-app-users'
});
} catch (error) {
throw new Error('Invalid access token');
}
}
verifyRefreshToken(token) {
try {
return jwt.verify(token, this.refreshTokenSecret, {
issuer: 'your-app',
audience: 'your-app-users'
});
} catch (error) {
throw new Error('Invalid refresh token');
}
}
}
// セキュアなCookie設定
app.use(session({
secret: process.env.SESSION_SECRET,
name: 'sessionId', // デフォルト名を変更
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS必須
httpOnly: true, // XSS対策
maxAge: 1000 * 60 * 60 * 24, // 24時間
sameSite: 'strict' // CSRF対策
},
store: new RedisStore({
client: redisClient,
prefix: 'sess:'
})
}));
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
class MFAManager {
generateSecret(userEmail) {
const secret = speakeasy.generateSecret({
name: userEmail,
issuer: 'Your App Name',
length: 32
});
return {
secret: secret.base32,
qrCodeUrl: secret.otpauth_url
};
}
async generateQRCode(otpauthUrl) {
return await QRCode.toDataURL(otpauthUrl);
}
verifyToken(token, secret) {
return speakeasy.totp.verify({
secret: secret,
encoding: 'base32',
token: token,
window: 2 // 時間窓を2つ許可(前後1分)
});
}
generateBackupCodes() {
const codes = [];
for (let i = 0; i < 10; i++) {
codes.push(crypto.randomBytes(4).toString('hex').toUpperCase());
}
return codes;
}
}
// MFA設定エンドポイント
app.post('/api/mfa/setup', authenticateToken, async (req, res) => {
const userId = req.user.id;
const mfaManager = new MFAManager();
const { secret, qrCodeUrl } = mfaManager.generateSecret(req.user.email);
const qrCode = await mfaManager.generateQRCode(qrCodeUrl);
// 一時的にシークレットを保存(確認後に永続化)
await redis.setex(`mfa_setup:${userId}`, 300, secret);
res.json({
qrCode,
secret // バックアップ用
});
});
// MFA確認エンドポイント
app.post('/api/mfa/verify', authenticateToken, async (req, res) => {
const { token } = req.body;
const userId = req.user.id;
const mfaManager = new MFAManager();
const secret = await redis.get(`mfa_setup:${userId}`);
if (!secret) {
return res.status(400).json({ error: 'MFA setup not found' });
}
const isValid = mfaManager.verifyToken(token, secret);
if (!isValid) {
return res.status(400).json({ error: 'Invalid token' });
}
// MFAを有効化
const backupCodes = mfaManager.generateBackupCodes();
await updateUser(userId, {
mfaSecret: secret,
mfaEnabled: true,
backupCodes: backupCodes.map(code => bcrypt.hashSync(code, 10))
});
await redis.del(`mfa_setup:${userId}`);
res.json({
success: true,
backupCodes
});
});
// Greenlock Express を使用した自動HTTPS
const greenlock = require('greenlock-express');
greenlock.init({
packageRoot: __dirname,
configDir: './greenlock.d',
maintainerEmail: 'admin@example.com',
cluster: false
}).serve(app);
// 手動でのHTTPS設定
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('path/to/private-key.pem'),
cert: fs.readFileSync('path/to/certificate.pem'),
// TLS設定
secureProtocol: 'TLSv1_2_method',
ciphers: [
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-SHA256',
'ECDHE-RSA-AES256-SHA384'
].join(':'),
honorCipherOrder: true
};
https.createServer(options, app).listen(443, () => {
console.log('HTTPS Server running on port 443');
});
// HTTP to HTTPS リダイレクト
const http = require('http');
http.createServer((req, res) => {
res.writeHead(301, {
'Location': `https://${req.headers.host}${req.url}`
});
res.end();
}).listen(80);
const winston = require('winston');
// セキュリティ専用ログ
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'security.log' }),
new winston.transports.Console()
]
});
// セキュリティイベント監視ミドルウェア
function securityMonitoring(req, res, next) {
const startTime = Date.now();
// 疑わしいパターンの検出
const suspiciousPatterns = [
/(\<script\>|\<\/script\>)/i,
/(union|select|insert|delete|drop|create|alter)/i,
/(\.\.|\/etc\/passwd|\/etc\/shadow)/i
];
const requestData = JSON.stringify({
url: req.url,
body: req.body,
query: req.query,
headers: req.headers
});
for (const pattern of suspiciousPatterns) {
if (pattern.test(requestData)) {
securityLogger.warn('Suspicious request detected', {
ip: req.ip,
userAgent: req.get('User-Agent'),
url: req.url,
pattern: pattern.toString(),
timestamp: new Date().toISOString()
});
break;
}
}
// レスポンス時間の監視
res.on('finish', () => {
const duration = Date.now() - startTime;
if (duration > 5000) { // 5秒以上
securityLogger.warn('Slow response detected', {
ip: req.ip,
url: req.url,
duration,
statusCode: res.statusCode
});
}
});
next();
}
app.use(securityMonitoring);
// レート制限
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 100, // リクエスト数制限
message: 'Too many requests from this IP',
standardHeaders: true,
legacyHeaders: false,
handler: (req, res) => {
securityLogger.warn('Rate limit exceeded', {
ip: req.ip,
userAgent: req.get('User-Agent'),
url: req.url
});
res.status(429).json({ error: 'Too many requests' });
}
});
app.use('/api/', limiter);
これらの対策を実装することで:
2025 年の Web セキュリティは、多層防御と継続的な監視が重要です。
重要なポイント:
セキュリティは一度設定すれば終わりではなく、継続的な改善が必要です。定期的な脆弱性診断と最新の脅威情報への対応を心がけましょう。
インラインスクリプトやスタイルがブロックされる
APIアクセスが拒否される
ログイン状態が維持されない
無限リダイレクトが発生
// 段階的なCSP導入戦略
const cspPhases = [
{
phase: 1,
description: 'Report-Onlyモードで違反を収集',
config: {
reportOnly: true,
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"], // 一時的に許可
reportUri: '/csp-report'
}
}
},
{
phase: 2,
description: 'インラインスクリプトをnonce化',
config: {
reportOnly: false,
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`]
}
}
},
{
phase: 3,
description: '完全なCSP適用',
config: {
reportOnly: false,
directives: {
defaultSrc: ["'none'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"]
}
}
}
];
// CSP違反レポートの分析
class CSPReportAnalyzer {
constructor() {
this.violations = new Map();
}
analyze(report) {
const key = `${report['violated-directive']}:${report['blocked-uri']}`;
if (!this.violations.has(key)) {
this.violations.set(key, {
directive: report['violated-directive'],
blockedUri: report['blocked-uri'],
count: 0,
samples: []
});
}
const violation = this.violations.get(key);
violation.count++;
if (violation.samples.length < 5) {
violation.samples.push({
documentUri: report['document-uri'],
referrer: report['referrer'],
timestamp: new Date().toISOString()
});
}
}
getTopViolations(limit = 10) {
return Array.from(this.violations.values())
.sort((a, b) => b.count - a.count)
.slice(0, limit);
}
}
// 環境別CORS設定
const corsOptions = {
development: {
origin: [
'http://localhost:3000',
'http://localhost:3001',
'http://127.0.0.1:3000'
],
credentials: true,
optionsSuccessStatus: 200
},
staging: {
origin: [
'https://staging.example.com',
'https://preview.example.com'
],
credentials: true,
optionsSuccessStatus: 200
},
production: {
origin: (origin, callback) => {
const allowedOrigins = [
'https://example.com',
'https://www.example.com',
'https://app.example.com'
];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
optionsSuccessStatus: 200,
maxAge: 86400 // 24時間
}
};
app.use(cors(corsOptions[process.env.NODE_ENV]));
// プリフライトリクエストの最適化
app.options('*', cors(corsOptions[process.env.NODE_ENV]));
// セッション診断ミドルウェア
function sessionDiagnostics(req, res, next) {
const sessionInfo = {
id: req.sessionID,
isNew: req.session.isNew,
cookie: {
maxAge: req.session.cookie.maxAge,
expires: req.session.cookie.expires,
httpOnly: req.session.cookie.httpOnly,
secure: req.session.cookie.secure,
sameSite: req.session.cookie.sameSite
},
data: Object.keys(req.session).filter(key => key !== 'cookie')
};
// 開発環境でのみログ出力
if (process.env.NODE_ENV === 'development') {
console.log('Session Diagnostics:', sessionInfo);
}
// セッション問題の検出
if (req.session.cookie.secure && req.protocol !== 'https') {
console.warn('Secure cookie over HTTP detected!');
}
if (req.session.cookie.sameSite === 'none' && !req.session.cookie.secure) {
console.error('SameSite=None requires Secure attribute!');
}
next();
}
// セッション修復
function fixSession(req, res, next) {
if (req.session && req.session.regenerate) {
req.session.regenerate((err) => {
if (err) {
console.error('Session regeneration failed:', err);
}
next();
});
} else {
next();
}
}
セキュリティ機能 | 初回読み込み時間への影響 | メモリ使用量への影響 | セキュリティ向上度 | 推奨度 |
---|---|---|---|---|
CSP (基本) | +5ms | +0.5MB | 高 | ★★★★★ |
CSP (厳格) | +15ms | +1MB | 非常に高 | ★★★★☆ |
HSTS | +0ms | +0MB | 高 | ★★★★★ |
セキュアCookie | +2ms | +0.1MB | 高 | ★★★★★ |
CORS (厳格) | +10ms | +0.2MB | 中 | ★★★★☆ |
サブリソース完全性 | +20ms | +0.5MB | 中 | ★★★☆☆ |
// キャッシュとセキュリティの最適化
const securityOptimizedCache = {
// 静的リソース用(厳格なキャッシュ)
staticAssets: {
'Cache-Control': 'public, max-age=31536000, immutable',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY'
},
// APIレスポンス用(キャッシュなし)
apiResponses: {
'Cache-Control': 'no-store, no-cache, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'X-Content-Type-Options': 'nosniff'
},
// HTML用(短期キャッシュ)
htmlPages: {
'Cache-Control': 'private, no-cache',
'X-Frame-Options': 'SAMEORIGIN',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin'
}
};
// リソースごとに適切なヘッダーを適用
app.use((req, res, next) => {
const path = req.path;
if (path.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$/)) {
Object.entries(securityOptimizedCache.staticAssets).forEach(([key, value]) => {
res.setHeader(key, value);
});
} else if (path.startsWith('/api/')) {
Object.entries(securityOptimizedCache.apiResponses).forEach(([key, value]) => {
res.setHeader(key, value);
});
} else {
Object.entries(securityOptimizedCache.htmlPages).forEach(([key, value]) => {
res.setHeader(key, value);
});
}
next();
});
// セキュリティメトリクス収集
class SecurityMetrics {
constructor() {
this.metrics = {
authAttempts: { success: 0, failed: 0 },
cspViolations: 0,
rateLimitHits: 0,
suspiciousRequests: 0,
tlsErrors: 0
};
}
track(event, data = {}) {
switch (event) {
case 'auth_success':
this.metrics.authAttempts.success++;
break;
case 'auth_failed':
this.metrics.authAttempts.failed++;
break;
case 'csp_violation':
this.metrics.cspViolations++;
break;
case 'rate_limit':
this.metrics.rateLimitHits++;
break;
case 'suspicious_request':
this.metrics.suspiciousRequests++;
break;
case 'tls_error':
this.metrics.tlsErrors++;
break;
}
// アラートの判定
this.checkAlerts();
}
checkAlerts() {
const failureRate = this.metrics.authAttempts.failed /
(this.metrics.authAttempts.success + this.metrics.authAttempts.failed);
if (failureRate > 0.5 && this.metrics.authAttempts.failed > 100) {
this.sendAlert('High authentication failure rate detected');
}
if (this.metrics.suspiciousRequests > 1000) {
this.sendAlert('Potential attack detected: high volume of suspicious requests');
}
}
sendAlert(message) {
// アラート送信ロジック
console.error(`SECURITY ALERT: ${message}`);
// メール、Slack、PagerDutyなどへの通知
}
getReport() {
return {
timestamp: new Date().toISOString(),
metrics: this.metrics,
health: this.calculateHealthScore()
};
}
calculateHealthScore() {
// 0-100のスコアを計算
let score = 100;
// 認証失敗率に基づく減点
const authFailureRate = this.metrics.authAttempts.failed /
(this.metrics.authAttempts.success + this.metrics.authAttempts.failed || 1);
score -= authFailureRate * 30;
// CSP違反に基づく減点
score -= Math.min(this.metrics.cspViolations / 100, 10);
// レート制限ヒットに基づく減点
score -= Math.min(this.metrics.rateLimitHits / 1000, 20);
return Math.max(0, Math.round(score));
}
}
const securityMetrics = new SecurityMetrics();
// メトリクスダッシュボードエンドポイント
app.get('/admin/security-metrics', authenticate, requireRole(['admin']), (req, res) => {
res.json(securityMetrics.getReport());
});
□ すべての依存関係が最新バージョンに更新されている
npm audit
でセキュリティ脆弱性をチェックnpm outdated
で古いパッケージを確認□ 環境変数と機密情報の管理
.env
ファイルが gitignore されている□ セキュリティヘッダーの設定
□ 認証・認可の実装
□ 入力検証とサニタイゼーション
□ HTTPS とTLS の設定
□ ログとモニタリング
□ エラーハンドリング
// Zero Trust 原則に基づく認証フロー
class ZeroTrustAuth {
constructor() {
this.contextFactors = [
'deviceId',
'location',
'timeOfAccess',
'requestPattern',
'userBehavior'
];
}
async authenticate(request) {
const riskScore = await this.calculateRiskScore(request);
const authRequirements = this.determineAuthRequirements(riskScore);
return this.performAuthentication(request, authRequirements);
}
async calculateRiskScore(request) {
let score = 0;
// デバイスの信頼性
if (!this.isKnownDevice(request.deviceId)) {
score += 30;
}
// 位置情報の異常
if (await this.isLocationAnomaly(request.ip, request.userId)) {
score += 40;
}
// アクセス時間の異常
if (this.isTimeAnomaly(request.timestamp, request.userId)) {
score += 20;
}
// リクエストパターンの異常
if (await this.isRequestPatternAnomaly(request)) {
score += 30;
}
return Math.min(score, 100);
}
determineAuthRequirements(riskScore) {
if (riskScore < 20) {
return { methods: ['password'] };
} else if (riskScore < 50) {
return { methods: ['password', 'mfa'] };
} else if (riskScore < 80) {
return { methods: ['password', 'mfa', 'biometric'] };
} else {
return { methods: ['password', 'mfa', 'biometric', 'adminApproval'] };
}
}
}
「セキュリティは機能ではなく、プロセスです。継続的な改善と監視が、真のセキュリティを実現します。」