From latestaiagents
OWASP A10 - Insufficient Logging and Monitoring. Use this skill when implementing audit logs, security monitoring, or incident detection. Activate when: logging, audit trail, security events, monitoring, alerting, SIEM, incident detection, log analysis, security logging, breach detection.
npx claudepluginhub latestaiagents/agent-skills --plugin skills-authoringThis skill uses the workspace's default tool permissions.
**Implement comprehensive logging and monitoring to detect and respond to security incidents.**
Implements structured security logging and monitoring with Pino to detect attacks, log auth events, redact sensitive data, and support incident response and compliance audits.
Detects logging failures including log injection (CWE-117), insufficient logging, secrets in logs, and audit trail issues in Python, Java, Go, TypeScript, and PHP during whitebox pentesting.
Analyzes PHP code for OWASP A09:2021 logging failures: log injection, PII/sensitive data exposure, missing audit trails for security events like password resets.
Share bugs, ideas, or general feedback.
Implement comprehensive logging and monitoring to detect and respond to security incidents.
| 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 |
// NEVER log these:
const NEVER_LOG = [
'passwords',
'credit_card_numbers',
'ssn',
'api_keys',
'tokens',
'session_ids',
'private_keys',
'health_information'
];
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;
}
// 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 });
}
// 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()
});
}
// 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);
}
// 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;
}
// 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
});
}
}
// 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 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
}