@ordojs/security
Version:
Security package for OrdoJS with XSS, CSRF, and injection protection
245 lines • 8.22 kB
JavaScript
export class RuntimeSecurityMonitor {
events = [];
options;
alertCounts = {};
startTime;
constructor(options = {}) {
this.options = {
enableLogging: true,
logLevel: 'warn',
maxEvents: 10000,
alertThresholds: {
critical: 1,
high: 5,
medium: 10,
},
...options,
};
this.startTime = new Date();
}
recordEvent(event) {
const securityEvent = {
id: this.generateEventId(),
timestamp: new Date(),
...event,
};
this.events.push(securityEvent);
// Maintain max events limit
if (this.events.length > this.options.maxEvents) {
this.events.shift();
}
// Log event if enabled
if (this.options.enableLogging) {
this.logEvent(securityEvent);
}
// Check alert thresholds
this.checkAlertThresholds(securityEvent);
// Update metrics
if (this.options.onMetricsUpdate) {
this.options.onMetricsUpdate(this.getMetrics());
}
}
recordXSSAttempt(details) {
this.recordEvent({
type: 'xss_attempt',
severity: 'high',
source: details.source,
details: {
payload: details.payload,
context: details.context,
},
blocked: details.blocked,
});
}
recordCSRFViolation(details) {
this.recordEvent({
type: 'csrf_violation',
severity: 'high',
source: details.source,
details: {
endpoint: details.endpoint,
hasExpectedToken: !!details.expectedToken,
hasReceivedToken: !!details.receivedToken,
},
blocked: true,
});
}
recordInjectionAttempt(details) {
this.recordEvent({
type: 'injection_attempt',
severity: 'critical',
source: details.source,
details: {
injectionType: details.type,
payload: details.payload,
query: details.query,
},
blocked: details.blocked,
});
}
recordRateLimitExceeded(details) {
this.recordEvent({
type: 'rate_limit_exceeded',
severity: 'medium',
source: details.source,
details: {
limit: details.limit,
current: details.current,
window: details.window,
endpoint: details.endpoint,
},
blocked: true,
});
}
recordAuthFailure(details) {
this.recordEvent({
type: 'auth_failure',
severity: details.reason === 'insufficient_permissions' ? 'medium' : 'high',
source: details.source,
details: {
reason: details.reason,
username: details.username,
endpoint: details.endpoint,
},
blocked: true,
});
}
recordSuspiciousActivity(details) {
const severity = details.riskScore >= 80 ? 'critical' :
details.riskScore >= 60 ? 'high' :
details.riskScore >= 40 ? 'medium' : 'low';
this.recordEvent({
type: 'suspicious_activity',
severity,
source: details.source,
details: {
activity: details.activity,
riskScore: details.riskScore,
context: details.context,
},
blocked: details.riskScore >= 80,
});
}
getMetrics() {
const now = new Date();
const eventsByType = {};
const eventsBySeverity = {};
const sourceIpCounts = {};
let totalEvents = 0;
let blockedEvents = 0;
this.events.forEach(event => {
totalEvents++;
if (event.blocked)
blockedEvents++;
eventsByType[event.type] = (eventsByType[event.type] || 0) + 1;
eventsBySeverity[event.severity] = (eventsBySeverity[event.severity] || 0) + 1;
if (event.source.ip) {
sourceIpCounts[event.source.ip] = (sourceIpCounts[event.source.ip] || 0) + 1;
}
});
const topSources = Object.entries(sourceIpCounts)
.map(([ip, count]) => ({ ip, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 10);
return {
totalEvents,
blockedEvents,
eventsByType,
eventsBySeverity,
topSources,
timeRange: {
start: this.startTime,
end: now,
},
};
}
getEvents(filter) {
let filteredEvents = [...this.events];
if (filter?.type) {
filteredEvents = filteredEvents.filter(e => e.type === filter.type);
}
if (filter?.severity) {
filteredEvents = filteredEvents.filter(e => e.severity === filter.severity);
}
if (filter?.since) {
filteredEvents = filteredEvents.filter(e => e.timestamp >= filter.since);
}
if (filter?.limit) {
filteredEvents = filteredEvents.slice(-filter.limit);
}
return filteredEvents.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
}
clearEvents() {
this.events = [];
this.alertCounts = {};
}
exportEvents(format = 'json') {
if (format === 'csv') {
const headers = ['id', 'type', 'severity', 'timestamp', 'blocked', 'source_ip', 'user_agent', 'details'];
const rows = this.events.map(event => [
event.id,
event.type,
event.severity,
event.timestamp.toISOString(),
event.blocked.toString(),
event.source.ip || '',
event.source.userAgent || '',
JSON.stringify(event.details),
]);
return [headers, ...rows].map(row => row.join(',')).join('\n');
}
return JSON.stringify(this.events, null, 2);
}
generateEventId() {
return `sec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
logEvent(event) {
const logLevel = this.getLogLevel(event.severity);
if (!this.shouldLog(logLevel))
return;
const message = `Security Event [${event.type}]: ${event.severity.toUpperCase()} - ${JSON.stringify(event.details)}`;
switch (logLevel) {
case 'error':
console.error(message);
break;
case 'warn':
console.warn(message);
break;
case 'info':
console.info(message);
break;
case 'debug':
console.debug(message);
break;
}
}
getLogLevel(severity) {
switch (severity) {
case 'critical': return 'error';
case 'high': return 'error';
case 'medium': return 'warn';
case 'low': return 'info';
default: return 'debug';
}
}
shouldLog(level) {
const levels = ['debug', 'info', 'warn', 'error'];
const currentLevelIndex = levels.indexOf(this.options.logLevel);
const eventLevelIndex = levels.indexOf(level);
return eventLevelIndex >= currentLevelIndex;
}
checkAlertThresholds(event) {
const thresholds = this.options.alertThresholds;
const threshold = event.severity === 'low' ? undefined : thresholds[event.severity];
if (!threshold)
return;
const key = `${event.severity}_${event.type}`;
this.alertCounts[key] = (this.alertCounts[key] || 0) + 1;
if (this.alertCounts[key] >= threshold && this.options.onAlert) {
this.options.onAlert(event);
}
}
}
// Singleton instance for global use
export const runtimeMonitor = new RuntimeSecurityMonitor();
//# sourceMappingURL=runtime-monitor.js.map