logging-monitoring
SKILL.md
Security Logging & Monitoring (OWASP A10)
Implement comprehensive logging and monitoring to detect and respond to security incidents.
When to Use
- Setting up application logging
- Implementing audit trails
- Configuring security alerting
- Building incident detection
- Compliance requirements (SOC2, GDPR)
- Post-incident forensics
Critical Events to Log
| Event Category | Examples | Priority |
|---|---|---|
| Authentication | Login success/failure, logout, MFA events | HIGH |
| Authorization | Access denied, privilege changes | HIGH |
| Data Access | Sensitive data reads, exports | HIGH |
| Data Modification | Create, update, delete operations | MEDIUM |
| Security Events | Input validation failures, rate limits | HIGH |
| System Events | Startup, shutdown, errors | MEDIUM |
| Admin Actions | Config changes, user management | HIGH |
What NOT to Log
// NEVER log these:
const NEVER_LOG = [
'passwords',
'credit_card_numbers',
'ssn',
'api_keys',
'tokens',
'session_ids',
'private_keys',
'health_information'
];
Secure Logging Implementation
1. Structured Security Logger
const winston = require('winston');
// Security event types
const SecurityEventType = {
AUTH_SUCCESS: 'AUTH_SUCCESS',
AUTH_FAILURE: 'AUTH_FAILURE',
AUTH_LOGOUT: 'AUTH_LOGOUT',
ACCESS_DENIED: 'ACCESS_DENIED',
PRIVILEGE_ESCALATION: 'PRIVILEGE_ESCALATION',
DATA_ACCESS: 'DATA_ACCESS',
DATA_EXPORT: 'DATA_EXPORT',
INPUT_VALIDATION_FAILURE: 'INPUT_VALIDATION_FAILURE',
RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
SUSPICIOUS_ACTIVITY: 'SUSPICIOUS_ACTIVITY',
ADMIN_ACTION: 'ADMIN_ACTION',
CONFIG_CHANGE: 'CONFIG_CHANGE'
};
// Security logger configuration
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'ISO' }),
winston.format.json()
),
defaultMeta: {
service: 'my-app',
environment: process.env.NODE_ENV
},
transports: [
// Security events to dedicated file
new winston.transports.File({
filename: 'logs/security.log',
level: 'info'
}),
// Critical events to separate file
new winston.transports.File({
filename: 'logs/security-critical.log',
level: 'warn'
}),
// Send to SIEM (example: Splunk HEC)
new winston.transports.Http({
host: 'splunk.example.com',
port: 8088,
path: '/services/collector',
ssl: true
})
]
});
// Log security event function
function logSecurityEvent(eventType, details) {
const event = {
eventType,
timestamp: new Date().toISOString(),
...sanitizeForLogging(details)
};
// Determine log level based on event type
const criticalEvents = [
SecurityEventType.AUTH_FAILURE,
SecurityEventType.ACCESS_DENIED,
SecurityEventType.PRIVILEGE_ESCALATION,
SecurityEventType.SUSPICIOUS_ACTIVITY
];
if (criticalEvents.includes(eventType)) {
securityLogger.warn(event);
} else {
securityLogger.info(event);
}
return event;
}
2. Authentication Event Logging
// Login attempt logging
async function handleLogin(req, res) {
const { email, password } = req.body;
const clientIp = req.ip;
const userAgent = req.headers['user-agent'];
try {
const user = await authenticateUser(email, password);
logSecurityEvent(SecurityEventType.AUTH_SUCCESS, {
userId: user.id,
email: maskEmail(email),
ip: clientIp,
userAgent,
method: 'password',
mfaUsed: false
});
// Continue with login...
} catch (error) {
logSecurityEvent(SecurityEventType.AUTH_FAILURE, {
email: maskEmail(email),
ip: clientIp,
userAgent,
reason: error.code, // e.g., 'INVALID_PASSWORD', 'USER_NOT_FOUND'
attemptCount: await getFailedAttemptCount(email)
});
// Don't reveal if user exists
res.status(401).json({ error: 'Invalid credentials' });
}
}
// Logout logging
function handleLogout(req, res) {
logSecurityEvent(SecurityEventType.AUTH_LOGOUT, {
userId: req.user.id,
sessionDuration: Date.now() - req.session.loginTime,
ip: req.ip
});
req.session.destroy();
res.json({ success: true });
}
3. Authorization Event Logging
// Access control logging middleware
function logAccessControl(req, res, next) {
const originalJson = res.json.bind(res);
res.json = (body) => {
// Log access denied
if (res.statusCode === 403) {
logSecurityEvent(SecurityEventType.ACCESS_DENIED, {
userId: req.user?.id,
path: req.path,
method: req.method,
ip: req.ip,
requiredRole: req.requiredRole,
userRole: req.user?.role
});
}
return originalJson(body);
};
next();
}
// Privilege change logging
async function changeUserRole(adminId, targetUserId, newRole) {
const oldRole = await getUserRole(targetUserId);
await updateUserRole(targetUserId, newRole);
logSecurityEvent(SecurityEventType.ADMIN_ACTION, {
action: 'ROLE_CHANGE',
adminId,
targetUserId,
oldRole,
newRole,
timestamp: new Date().toISOString()
});
}
4. Data Access Logging
// Sensitive data access logging
async function getSensitiveUserData(requesterId, targetUserId) {
const data = await db.users.findById(targetUserId, {
include: ['ssn', 'taxId', 'bankAccount']
});
logSecurityEvent(SecurityEventType.DATA_ACCESS, {
requesterId,
targetUserId,
dataType: 'sensitive_pii',
fields: ['ssn', 'taxId', 'bankAccount'],
reason: 'customer_support_request',
ticketId: 'TICKET-123'
});
return data;
}
// Data export logging
async function exportUserData(adminId, filters) {
const recordCount = await db.users.count(filters);
logSecurityEvent(SecurityEventType.DATA_EXPORT, {
adminId,
exportType: 'user_data',
filters: sanitizeForLogging(filters),
recordCount,
format: 'csv'
});
return generateExport(filters);
}
5. Input Validation Failure Logging
// Log validation failures (potential attack indicators)
function validateAndLog(schema, data, req) {
const result = schema.validate(data);
if (result.error) {
logSecurityEvent(SecurityEventType.INPUT_VALIDATION_FAILURE, {
path: req.path,
method: req.method,
ip: req.ip,
userId: req.user?.id,
validationErrors: result.error.details.map(d => ({
field: d.path.join('.'),
message: d.message,
// Don't log the actual invalid value (might be attack payload)
valueLength: String(d.context?.value).length
}))
});
}
return result;
}
6. Rate Limiting & Suspicious Activity
// Rate limit exceeded logging
const rateLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
handler: (req, res) => {
logSecurityEvent(SecurityEventType.RATE_LIMIT_EXCEEDED, {
ip: req.ip,
path: req.path,
userId: req.user?.id,
windowMs: 15 * 60 * 1000,
requestCount: req.rateLimit.current
});
res.status(429).json({ error: 'Too many requests' });
}
});
// Suspicious activity detection
function detectSuspiciousActivity(req) {
const indicators = [];
// Multiple failed logins
if (req.failedLoginCount > 5) {
indicators.push('multiple_failed_logins');
}
// Unusual user agent
if (!req.headers['user-agent'] || req.headers['user-agent'].includes('sqlmap')) {
indicators.push('suspicious_user_agent');
}
// Geographic anomaly
if (req.geoLocation !== req.user?.lastKnownLocation) {
indicators.push('geographic_anomaly');
}
if (indicators.length > 0) {
logSecurityEvent(SecurityEventType.SUSPICIOUS_ACTIVITY, {
userId: req.user?.id,
ip: req.ip,
indicators,
path: req.path
});
}
}
7. Alerting Configuration
// Alert on critical security events
const alertThresholds = {
AUTH_FAILURE: { count: 10, windowMinutes: 5 },
ACCESS_DENIED: { count: 20, windowMinutes: 10 },
RATE_LIMIT_EXCEEDED: { count: 5, windowMinutes: 1 },
SUSPICIOUS_ACTIVITY: { count: 1, windowMinutes: 1 } // Immediate
};
async function checkAlertThreshold(eventType, identifier) {
const threshold = alertThresholds[eventType];
if (!threshold) return;
const count = await redis.incr(`alert:${eventType}:${identifier}`);
await redis.expire(`alert:${eventType}:${identifier}`, threshold.windowMinutes * 60);
if (count >= threshold.count) {
await sendSecurityAlert({
eventType,
identifier,
count,
windowMinutes: threshold.windowMinutes,
severity: count > threshold.count * 2 ? 'CRITICAL' : 'HIGH'
});
}
}
async function sendSecurityAlert(alert) {
// Send to Slack
await slack.send({
channel: '#security-alerts',
text: `🚨 Security Alert: ${alert.eventType}`,
attachments: [{
color: alert.severity === 'CRITICAL' ? 'danger' : 'warning',
fields: [
{ title: 'Event', value: alert.eventType },
{ title: 'Count', value: String(alert.count) },
{ title: 'Window', value: `${alert.windowMinutes} minutes` }
]
}]
});
// Send to PagerDuty for critical
if (alert.severity === 'CRITICAL') {
await pagerduty.trigger({
routing_key: process.env.PAGERDUTY_KEY,
event_action: 'trigger',
payload: {
summary: `Security Alert: ${alert.eventType}`,
severity: 'critical',
source: 'security-monitoring'
}
});
}
}
Log Retention & Compliance
// Log retention policy
const retentionPolicy = {
security: '1 year', // Security events
audit: '7 years', // Compliance audit trail
application: '90 days', // General application logs
debug: '7 days' // Debug logs
};
// GDPR-compliant log anonymization
function anonymizeOldLogs(olderThanDays) {
// Replace PII with anonymized values
// Keep event structure for security analysis
}
Code Review Checklist
- Authentication events logged (success and failure)
- Authorization failures logged
- Sensitive data access logged
- Admin actions logged
- Input validation failures logged
- No sensitive data in logs
- Logs are tamper-evident (signed/append-only)
- Log retention policy defined
- Alerting configured for critical events
- Logs sent to centralized SIEM
- Log access is restricted and audited
Best Practices
- Log All Security Events: Don't skip auth, access, and admin actions
- Sanitize Log Data: Never log passwords, tokens, or PII
- Use Structured Logging: JSON format for easy parsing
- Centralize Logs: Send to SIEM for correlation
- Set Up Alerts: Don't just collect, actively monitor
- Protect Log Integrity: Prevent tampering
- Define Retention: Balance security needs with privacy
- Test Detection: Regularly test if you'd catch an attack
Weekly Installs
2
Repository
latestaiagents/…t-skillsGitHub Stars
2
First Seen
Feb 4, 2026
Installed on
mcpjam2
claude-code2
replit2
junie2
windsurf2
zencoder2