skills/security/owasp-guardian/logging-monitoring/SKILL.md
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 skillsauth add latestaiagents/agent-skills logging-monitoringInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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
}
development
Test skills for correct activation, content quality, and regression — both automated checks (frontmatter validity, lint) and manual verification (query-suite activation testing). Covers CI integration and how to catch skill regressions before users do. Use this skill when adding skills to a repo, setting up CI for a skill library, or debugging "the skill exists but doesn't work". Activate when: test skills, validate skills, skill CI, skill linting, skill activation test, skill regression.
documentation
Write the YAML frontmatter for a SKILL.md file so it activates reliably — name, description, and activation keywords that the model matches against. Covers length, tone, and the most common frontmatter mistakes. Use this skill when authoring a new skill, fixing a skill that isn't auto-activating, or reviewing skills for publication. Activate when: SKILL.md frontmatter, skill description, skill activation, skill YAML, write a skill, author a skill.
development
Design skills that fire at the right moment — neither over-eager (noise) nor under-eager (silent). Covers activation specificity, trigger phrases, disambiguation between overlapping skills, and debugging activation. Use this skill when multiple skills could fire on the same query, a skill never fires, or a skill fires too often. Activate when: skill won't activate, skill over-activates, overlapping skills, skill triggers, skill selection, skill disambiguation.
development
Structure SKILL.md content so the model reads just enough — concise summary up front, progressively deeper detail, examples on demand. Covers section ordering, length budgets, when to split into multiple skills. Use this skill when writing or refactoring a skill body, one skill has grown too long, or a skill is wordy but not useful. Activate when: SKILL.md structure, skill content, skill too long, split skill, progressive disclosure, skill body.