19Deployment
Monitoring & Detection
Monitoring & Detection — Instruction 19
Coverage
Honeytokens, security.txt, logging, anomaly detection, secret rotation ASVS V7, ASVS Level 3 behavioral monitoring
Honeytokens
1. Deploy Canary Credentials
// Honeytokens: fake credentials that alert you when accessed
// If someone finds and uses them → you know there's a breach
// 🟢 Create fake AWS keys at canarytokens.org (free)
// Place them in a plausible location:
// .env.backup, config/old-settings.js, README.old.md
// Example in .env.backup (honeypot file):
STRIPE_SECRET=sk_live_HONEYPOT_xyz123
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7HONEYPOT
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/HONEYPOT/bPxRfiCYEXAMPLEKEY
// When attacker tries to use these → you receive an alert email
// Source: https://canarytokens.org
2. Honeytoken in Database
// Create fake admin account that should never be used
await User.create({
email: 'admin-backup@yourdomain.com', // never used in real ops
role: 'admin',
isHoneytoken: true
})
// If this account is accessed → alert immediately
User.findOne({ email }).then(user => {
if (user?.isHoneytoken) {
securityAlert('Honeytoken accessed!', { email, ip: req.ip })
}
})
3. Honeypot Form Fields
<!-- Honeypot field to detect bots -->
<form>
<input type="text" name="name" required>
<input type="email" name="email" required>
<!-- Honeypot: hidden from real users, bots fill it in -->
<input type="text" name="website" style="display:none" tabindex="-1" autocomplete="off">
<button type="submit">Submit</button>
</form>
// Server-side: reject if honeypot field is filled
app.post('/contact', (req, res) => {
if (req.body.website) { // real user won't fill this hidden field
return res.status(200).json({ ok: true }) // fake success, discard
}
processForm(req.body)
})
security.txt
4. security.txt File
// Check if /.well-known/security.txt exists
// RFC 9116 standard — allows security researchers to report vulnerabilities
// 🔴 Missing → researchers don't know how to report issues
// 🟢 Create (Level 2):
Content for .well-known/security.txt:
Contact: mailto:security@yourdomain.com
Expires: 2027-01-01T00:00:00.000Z
Preferred-Languages: en, fr
Policy: https://yourdomain.com/security-policy
Acknowledgments: https://yourdomain.com/hall-of-fame
Structured Logging
5. Security Event Logging
// 🔴 No security logging = blind to attacks
// 🟢 Log all security events (without PII)
const securityLog = {
// Authentication events
loginSuccess: (userId, ip) => logger.info({ event: 'auth.login_success', userId, ip }),
loginFailure: (email, ip, reason) => logger.warn({ event: 'auth.login_failure', ip, reason }),
// Never log the email itself in high-GDPR contexts
// Authorization events
accessDenied: (userId, resource, ip) => logger.warn({ event: 'authz.denied', userId, resource, ip }),
// Suspicious events
rateLimitHit: (ip, endpoint) => logger.warn({ event: 'security.rate_limit', ip, endpoint }),
invalidToken: (ip, endpoint) => logger.warn({ event: 'security.invalid_token', ip, endpoint }),
scanDetected: (ip, pattern) => logger.error({ event: 'security.scan_detected', ip, pattern }),
}
6. Error Logging Without Exposure
// 🔴 Stack traces sent to client
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message, stack: err.stack }) // 🔴
// 🟢 Log full error server-side, generic message to client
app.use((err, req, res, next) => {
const errorId = crypto.randomUUID()
logger.error({ errorId, err, url: req.url, method: req.method })
res.status(500).json({ error: 'Internal server error', errorId })
// errorId lets you find the log entry without exposing details
})
Anomaly Detection (ASVS Level 3)
7. Behavioral Monitoring Middleware
// Track request patterns to detect attacks
const requestTracker = new Map() // Use Redis in production
app.use((req, res, next) => {
const ip = req.ip
const now = Date.now()
const window = 60 * 1000 // 1 minute window
if (!requestTracker.has(ip)) {
requestTracker.set(ip, { count: 0, endpoints: new Set(), firstSeen: now })
}
const tracker = requestTracker.get(ip)
tracker.count++
tracker.endpoints.add(req.path)
// Detect endpoint scanning (many different endpoints from one IP)
if (tracker.endpoints.size > 30 && (now - tracker.firstSeen) < window) {
logger.warn({ event: 'security.scan_detected', ip, endpoints: tracker.endpoints.size })
}
// Log for SIEM analysis
logger.info({ event: 'request', ip, path: req.path, method: req.method, userId: req.user?.id })
next()
})
Secret Rotation Tracking
8. Rotation Reminders
// Track in memory-security.md and alert when rotation is due
// Check on every /security-status command:
function checkRotationStatus(memory) {
const now = Date.now()
const rotationDays = memory.rotation_reminder_days || 90
for (const [secretName, info] of Object.entries(memory.secrets || {})) {
const lastRotated = new Date(info.last_rotated).getTime()
const daysSince = (now - lastRotated) / (1000 * 60 * 60 * 24)
if (daysSince > rotationDays) {
console.warn(`⚠️ SECRET ROTATION DUE: ${secretName} (${Math.round(daysSince)} days)`)
}
}
}
Directory Listing
9. Disable Directory Listing
// 🔴 Web server lists files in directories without index
// → Exposes file structure, potential sensitive files
// Express: never use without explicit index
app.use(express.static('public', { index: false }))
// Better: use explicit routes for files
// Nginx:
// autoindex off; (this is the default, verify it's not enabled)
// Next.js: no directory listing by default ✅
Incident Alerting
10. Real-Time Security Alerts
// Implement alerts for critical security events
async function securityAlert(event, data) {
// Option 1: Email
await sendEmail({ to: process.env.SECURITY_EMAIL, subject: `🚨 SECURITY: ${event}`, body: JSON.stringify(data) })
// Option 2: Slack webhook
await fetch(process.env.SLACK_SECURITY_WEBHOOK, {
method: 'POST',
body: JSON.stringify({ text: `🚨 SECURITY EVENT: ${event}\n${JSON.stringify(data)}` })
})
// Log as critical regardless
logger.error({ event: 'security.alert', alertType: event, ...data })
}
// Trigger on:
// - Honeytoken accessed
// - Multiple auth failures (> 10 in 5 min)
// - Admin route accessed from new IP
// - Database error suggesting injection attempt
// - File outside allowed path accessed