Continuous Monitoring Implementation
Continuous Monitoring Implementation
Real-time Security Header Monitoring System
const EventEmitter = require('events');
class SecurityHeaderMonitor extends EventEmitter {
constructor(config) {
super();
this.config = config;
this.endpoints = config.endpoints || [];
this.interval = config.interval || 300000; // 5 minutes
this.thresholds = config.thresholds || {};
this.results = new Map();
}
start() {
console.log('Starting security header monitoring...');
this.check(); // Initial check
this.timer = setInterval(() => this.check(), this.interval);
}
stop() {
if (this.timer) {
clearInterval(this.timer);
console.log('Monitoring stopped');
}
}
async check() {
for (const endpoint of this.endpoints) {
try {
const result = await this.checkEndpoint(endpoint);
this.processResult(endpoint, result);
} catch (error) {
this.emit('error', { endpoint, error });
}
}
}
async checkEndpoint(endpoint) {
const response = await axios.get(endpoint.url, {
timeout: 10000,
validateStatus: () => true
});
const headers = response.headers;
const statusCode = response.status;
return {
timestamp: new Date(),
statusCode,
headers: this.extractSecurityHeaders(headers),
violations: this.checkViolations(headers, endpoint.expectedHeaders),
score: this.calculateScore(headers)
};
}
extractSecurityHeaders(headers) {
const securityHeaders = [
'content-security-policy',
'x-content-type-options',
'x-frame-options',
'strict-transport-security',
'referrer-policy',
'permissions-policy'
];
return securityHeaders.reduce((acc, header) => {
if (headers[header]) {
acc[header] = headers[header];
}
return acc;
}, {});
}
checkViolations(headers, expectedHeaders = {}) {
const violations = [];
Object.entries(expectedHeaders).forEach(([header, expected]) => {
const actual = headers[header];
if (!actual) {
violations.push({
type: 'missing',
header,
expected
});
} else if (actual !== expected && !this.isValidVariation(header, actual, expected)) {
violations.push({
type: 'mismatch',
header,
expected,
actual
});
}
});
return violations;
}
processResult(endpoint, result) {
const previous = this.results.get(endpoint.url);
this.results.set(endpoint.url, result);
// Check for changes
if (previous) {
const changes = this.detectChanges(previous, result);
if (changes.length > 0) {
this.emit('change', { endpoint, changes, result });
}
}
// Check for violations
if (result.violations.length > 0) {
this.emit('violation', { endpoint, violations: result.violations, result });
}
// Check score threshold
if (result.score < (this.thresholds.minScore || 80)) {
this.emit('low-score', { endpoint, score: result.score, result });
}
}
detectChanges(previous, current) {
const changes = [];
// Check for added/removed headers
const prevHeaders = Object.keys(previous.headers);
const currHeaders = Object.keys(current.headers);
currHeaders.forEach(header => {
if (!prevHeaders.includes(header)) {
changes.push({ type: 'added', header, value: current.headers[header] });
}
});
prevHeaders.forEach(header => {
if (!currHeaders.includes(header)) {
changes.push({ type: 'removed', header });
}
});
// Check for modified headers
currHeaders.forEach(header => {
if (prevHeaders.includes(header) &&
previous.headers[header] !== current.headers[header]) {
changes.push({
type: 'modified',
header,
oldValue: previous.headers[header],
newValue: current.headers[header]
});
}
});
return changes;
}
calculateScore(headers) {
let score = 100;
const penalties = {
missingCSP: 20,
missingHSTS: 15,
missingXFrameOptions: 10,
missingXContentTypeOptions: 10,
unsafeCSP: 15,
shortHSTS: 10
};
if (!headers['content-security-policy']) {
score -= penalties.missingCSP;
} else if (headers['content-security-policy'].includes('unsafe-inline')) {
score -= penalties.unsafeCSP;
}
if (!headers['strict-transport-security']) {
score -= penalties.missingHSTS;
} else {
const maxAge = headers['strict-transport-security'].match(/max-age=(\d+)/);
if (maxAge && parseInt(maxAge[1]) < 31536000) {
score -= penalties.shortHSTS;
}
}
if (!headers['x-frame-options']) score -= penalties.missingXFrameOptions;
if (!headers['x-content-type-options']) score -= penalties.missingXContentTypeOptions;
return Math.max(0, score);
}
}
// Usage with alerting
const monitor = new SecurityHeaderMonitor({
endpoints: [
{
url: 'https://example.com',
expectedHeaders: {
'x-content-type-options': 'nosniff',
'x-frame-options': 'DENY'
}
},
{
url: 'https://api.example.com',
expectedHeaders: {
'content-security-policy': "default-src 'self'"
}
}
],
interval: 300000, // 5 minutes
thresholds: {
minScore: 85
}
});
// Set up event handlers
monitor.on('violation', ({ endpoint, violations }) => {
console.error(`Security header violations detected at ${endpoint.url}:`, violations);
// Send alert to monitoring system
});
monitor.on('change', ({ endpoint, changes }) => {
console.warn(`Security headers changed at ${endpoint.url}:`, changes);
// Log to audit system
});
monitor.on('low-score', ({ endpoint, score }) => {
console.error(`Low security score (${score}) at ${endpoint.url}`);
// Trigger incident response
});
monitor.start();