Security Monitoring and Threat Detection
Build SIEM-like security monitoring with LogTide. Detect suspicious logins, brute force attacks, and security anomalies.
Traditional SIEM solutions are expensive and complex. LogTide provides the foundation for security monitoring at a fraction of the cost. This guide shows you how to detect threats, monitor for anomalies, and maintain audit trails.
Why Security Monitoring with LogTide?
- Cost-effective: No per-GB pricing that punishes security logging
- Self-hosted option: Keep security data in your infrastructure
- Real-time alerting: Immediate notification of security events
- Correlation: Link security events across your entire stack
- Compliance: Audit trails for SOC2, ISO27001, HIPAA
Security Logging Architecture
Data Sources:
- nginx/WAF: Access logs, blocked requests, rate limiting
- API Servers: Authentication events, authorization failures
- Database: Query audit logs, connection events
- Auth Service: Login attempts, password resets, MFA events
Processing:
- All security events flow into LogTide with severity levels
- Real-time detection rules analyze incoming logs
- Alerts trigger via Slack, PagerDuty, or webhooks
- Long-term archive to S3 for compliance
Implementation
1. Security Event Logging
Create a dedicated security logger:
// lib/security-logger.ts
import { LogTideClient } from '@logtide/sdk-node';
const client = new LogTideClient({
apiUrl: process.env.LOGTIDE_API_URL!,
apiKey: process.env.LOGTIDE_API_KEY!,
globalMetadata: {
service: 'security',
environment: process.env.NODE_ENV,
},
});
interface SecurityEvent {
event: string;
severity: 'low' | 'medium' | 'high' | 'critical';
userId?: string;
ip?: string;
userAgent?: string;
metadata?: Record<string, unknown>;
}
export const securityLogger = {
log(event: SecurityEvent) {
const level = event.severity === 'critical' || event.severity === 'high'
? 'error'
: event.severity === 'medium'
? 'warn'
: 'info';
client[level]('security', `SECURITY: ${event.event}`, {
security_event: event.event,
severity: event.severity,
user_id: event.userId,
source_ip: event.ip,
user_agent: event.userAgent,
...event.metadata,
});
},
// Convenience methods
loginSuccess(userId: string, ip: string, method: string) {
this.log({
event: 'login_success',
severity: 'low',
userId,
ip,
metadata: { method },
});
},
loginFailed(email: string, ip: string, reason: string) {
this.log({
event: 'login_failed',
severity: 'medium',
ip,
metadata: { email_hash: hashEmail(email), reason },
});
},
suspiciousActivity(event: string, details: Record<string, unknown>) {
this.log({
event,
severity: 'high',
...details,
});
},
criticalAlert(event: string, details: Record<string, unknown>) {
this.log({
event,
severity: 'critical',
...details,
});
},
};
2. Authentication Monitoring
// auth/login.ts
import { securityLogger } from '@/lib/security-logger';
import { RateLimiter } from '@/lib/rate-limiter';
const loginLimiter = new RateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts per window
});
app.post('/api/auth/login', async (req, res) => {
const { email, password } = req.body;
const ip = req.ip;
const userAgent = req.headers['user-agent'];
// Check rate limit
const rateLimitResult = await loginLimiter.check(ip);
if (!rateLimitResult.allowed) {
securityLogger.suspiciousActivity('brute_force_detected', {
ip,
userAgent,
attempts: rateLimitResult.attempts,
metadata: { email_hash: hashEmail(email) },
});
return res.status(429).json({
error: 'Too many login attempts. Please try again later.',
});
}
try {
const user = await authenticate(email, password);
if (!user) {
securityLogger.loginFailed(email, ip, 'invalid_credentials');
return res.status(401).json({ error: 'Invalid credentials' });
}
// Check for suspicious patterns
await checkLoginAnomalies(user, ip, userAgent);
securityLogger.loginSuccess(user.id, ip, 'password');
const token = generateToken(user);
res.json({ token });
} catch (error) {
securityLogger.log({
event: 'login_error',
severity: 'high',
ip,
metadata: {
email_hash: hashEmail(email),
error: error.message,
},
});
res.status(500).json({ error: 'Login failed' });
}
});
async function checkLoginAnomalies(user: User, ip: string, userAgent: string) {
// Check for new device
const knownDevices = await getKnownDevices(user.id);
const deviceFingerprint = generateFingerprint(userAgent);
if (!knownDevices.includes(deviceFingerprint)) {
securityLogger.log({
event: 'new_device_login',
severity: 'medium',
userId: user.id,
ip,
userAgent,
metadata: { fingerprint: deviceFingerprint },
});
// Optionally send notification to user
await notifyNewDeviceLogin(user, ip, userAgent);
}
// Check for impossible travel
const lastLogin = await getLastLogin(user.id);
if (lastLogin && isImpossibleTravel(lastLogin.ip, ip, lastLogin.time)) {
securityLogger.suspiciousActivity('impossible_travel', {
userId: user.id,
ip,
metadata: {
previous_ip: lastLogin.ip,
previous_time: lastLogin.time,
time_diff_minutes: (Date.now() - lastLogin.time) / 60000,
},
});
}
// Check for unusual time
const userTimezone = user.timezone || 'UTC';
const localHour = getLocalHour(userTimezone);
if (localHour >= 1 && localHour <= 5) {
securityLogger.log({
event: 'unusual_hour_login',
severity: 'low',
userId: user.id,
ip,
metadata: { local_hour: localHour, timezone: userTimezone },
});
}
}
3. API Rate Limiting and Abuse Detection
// middleware/rate-limit.ts
import { securityLogger } from '@/lib/security-logger';
interface RateLimitConfig {
windowMs: number;
max: number;
keyGenerator?: (req: Request) => string;
}
export function createRateLimiter(config: RateLimitConfig) {
const hits = new Map<string, { count: number; resetAt: number }>();
return async (req: Request, res: Response, next: NextFunction) => {
const key = config.keyGenerator?.(req) || req.ip;
const now = Date.now();
let record = hits.get(key);
if (!record || record.resetAt < now) {
record = { count: 0, resetAt: now + config.windowMs };
hits.set(key, record);
}
record.count++;
if (record.count > config.max) {
// Log rate limit violation
securityLogger.log({
event: 'rate_limit_exceeded',
severity: record.count > config.max * 2 ? 'high' : 'medium',
ip: req.ip,
metadata: {
path: req.path,
method: req.method,
attempts: record.count,
limit: config.max,
user_id: req.user?.id,
},
});
res.set('Retry-After', Math.ceil((record.resetAt - now) / 1000).toString());
return res.status(429).json({
error: 'Too many requests',
retryAfter: record.resetAt,
});
}
next();
};
}
// Apply different limits to different endpoints
app.use('/api/auth/*', createRateLimiter({
windowMs: 15 * 60 * 1000,
max: 10,
}));
app.use('/api/password-reset', createRateLimiter({
windowMs: 60 * 60 * 1000,
max: 3,
}));
app.use('/api/*', createRateLimiter({
windowMs: 60 * 1000,
max: 100,
keyGenerator: (req) => req.user?.id || req.ip,
}));
4. Privilege Escalation Monitoring
// middleware/audit-sensitive.ts
import { securityLogger } from '@/lib/security-logger';
const SENSITIVE_OPERATIONS = [
{ method: 'POST', path: '/api/users', event: 'user_created' },
{ method: 'DELETE', path: '/api/users/:id', event: 'user_deleted' },
{ method: 'PUT', path: '/api/users/:id/role', event: 'role_changed' },
{ method: 'POST', path: '/api/api-keys', event: 'api_key_created' },
{ method: 'DELETE', path: '/api/api-keys/:id', event: 'api_key_revoked' },
{ method: 'PUT', path: '/api/settings/security', event: 'security_settings_changed' },
];
export function auditSensitiveOperations(req: Request, res: Response, next: NextFunction) {
const operation = SENSITIVE_OPERATIONS.find(op =>
op.method === req.method && matchPath(op.path, req.path)
);
if (operation) {
// Log before operation
securityLogger.log({
event: `${operation.event}_attempted`,
severity: 'medium',
userId: req.user?.id,
ip: req.ip,
metadata: {
path: req.path,
method: req.method,
target_id: req.params.id,
},
});
// Capture response
const originalJson = res.json.bind(res);
res.json = function(body) {
// Log after operation
securityLogger.log({
event: res.statusCode < 400 ? operation.event : `${operation.event}_failed`,
severity: res.statusCode < 400 ? 'medium' : 'high',
userId: req.user?.id,
ip: req.ip,
metadata: {
path: req.path,
method: req.method,
target_id: req.params.id,
status_code: res.statusCode,
},
});
return originalJson(body);
};
}
next();
}
5. SQL Injection and XSS Detection
// middleware/input-validation.ts
import { securityLogger } from '@/lib/security-logger';
const SQL_INJECTION_PATTERNS = [
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER)\b)/i,
/(--|#|\/\*)/,
/(\bOR\b\s+\d+\s*=\s*\d+)/i,
/('\s*OR\s*')/i,
];
const XSS_PATTERNS = [
/<script\b[^>]*>/i,
/javascript:/i,
/on\w+\s*=/i,
/<iframe/i,
/<object/i,
];
export function detectMaliciousInput(req: Request, res: Response, next: NextFunction) {
const inputs = [
...Object.values(req.query),
...Object.values(req.params),
...(req.body ? Object.values(flattenObject(req.body)) : []),
].filter(v => typeof v === 'string');
for (const input of inputs) {
// Check for SQL injection
for (const pattern of SQL_INJECTION_PATTERNS) {
if (pattern.test(input)) {
securityLogger.criticalAlert('sql_injection_attempt', {
ip: req.ip,
userId: req.user?.id,
userAgent: req.headers['user-agent'],
metadata: {
path: req.path,
method: req.method,
pattern: pattern.source,
// Don't log the actual input to avoid log injection
input_length: input.length,
},
});
return res.status(400).json({ error: 'Invalid input' });
}
}
// Check for XSS
for (const pattern of XSS_PATTERNS) {
if (pattern.test(input)) {
securityLogger.suspiciousActivity('xss_attempt', {
ip: req.ip,
userId: req.user?.id,
userAgent: req.headers['user-agent'],
metadata: {
path: req.path,
method: req.method,
pattern: pattern.source,
},
});
return res.status(400).json({ error: 'Invalid input' });
}
}
}
next();
}
Detection Rules
Brute Force Attack
service:security AND security_event:login_failed
| window 5m
| group by source_ip
| count > 10
Alert: “Brute force attack detected from {source_ip}“
Account Takeover Attempt
service:security AND security_event:login_success AND new_device_login
| join (security_event:password_reset_requested) on user_id
| within 24h
Alert: “Possible account takeover for user {user_id}“
Privilege Escalation
service:security AND security_event:role_changed
| where metadata.new_role = "admin"
Alert: “User {target_id} promoted to admin by {user_id}“
API Abuse
service:security AND security_event:rate_limit_exceeded
| window 1h
| group by source_ip
| count > 50
Alert: “Sustained API abuse from {source_ip}“
Impossible Travel
service:security AND security_event:impossible_travel
Alert: “Impossible travel detected for user {user_id}“
Off-Hours Admin Activity
service:security AND severity:high
| where hour >= 22 OR hour <= 6
| where user_role = "admin"
Alert: “Admin activity outside business hours”
Real-Time Alerting
Slack Integration
// lib/security-alerts.ts
import { IncomingWebhook } from '@slack/webhook';
const webhook = new IncomingWebhook(process.env.SLACK_SECURITY_WEBHOOK!);
export async function sendSecurityAlert(event: SecurityEvent) {
if (event.severity !== 'high' && event.severity !== 'critical') {
return;
}
const color = event.severity === 'critical' ? '#FF0000' : '#FFA500';
await webhook.send({
attachments: [{
color,
title: `🚨 Security Alert: ${event.event}`,
fields: [
{ title: 'Severity', value: event.severity.toUpperCase(), short: true },
{ title: 'IP Address', value: event.ip || 'Unknown', short: true },
{ title: 'User ID', value: event.userId || 'N/A', short: true },
{ title: 'Time', value: new Date().toISOString(), short: true },
],
footer: 'LogTide Security Monitoring',
}],
});
}
PagerDuty Integration
// lib/pagerduty.ts
import { event } from '@pagerduty/pdjs';
export async function triggerIncident(securityEvent: SecurityEvent) {
if (securityEvent.severity !== 'critical') {
return;
}
await event({
data: {
routing_key: process.env.PAGERDUTY_ROUTING_KEY!,
event_action: 'trigger',
dedup_key: `security-${securityEvent.event}-${securityEvent.ip}`,
payload: {
summary: `Critical Security Event: ${securityEvent.event}`,
severity: 'critical',
source: 'LogTide Security Monitoring',
custom_details: {
event: securityEvent.event,
ip: securityEvent.ip,
user_id: securityEvent.userId,
...securityEvent.metadata,
},
},
},
});
}
Audit Trail for Compliance
SOC2 / ISO27001 Requirements
// All security events should include:
interface AuditEvent {
// Who
user_id: string;
user_email: string;
user_role: string;
// What
action: string;
resource_type: string;
resource_id: string;
// When
timestamp: string;
// Where
source_ip: string;
user_agent: string;
session_id: string;
// Result
success: boolean;
error_message?: string;
}
export function logAuditEvent(event: AuditEvent) {
client.info('audit', 'AUDIT', {
...event,
audit: true, // Flag for compliance queries
});
}
Retention Policy
For compliance, configure retention:
- Security events: 1 year minimum
- Audit logs: 7 years (financial services)
- Access logs: 90 days
Security Monitoring Checklist
-
Authentication Events
- Login success/failure with IP and device
- Password reset requests
- MFA enablement/disablement
- Session creation/termination
-
Authorization Events
- Role changes
- Permission grants/revokes
- API key creation/revocation
- Resource access attempts
-
Data Access
- Sensitive data queries
- Bulk exports
- PII access
-
Infrastructure
- Failed SSH attempts
- Firewall blocks
- Certificate errors
-
Anomaly Detection
- Impossible travel
- Unusual access patterns
- Off-hours activity
Common Pitfalls
1. “We log everything, we’re secure”
Logging without monitoring is useless.
Solution: Set up active alerts for critical events.
2. “Our logs contain too much noise”
Then you’ll miss the real threats.
Solution: Severity levels and filtered dashboards.
3. “We’ll investigate breaches after they happen”
By then it’s too late.
Solution: Real-time alerting for suspicious patterns.
Next Steps
- nginx Integration - Web server security logs
- Node.js SDK - Application security logging
- GDPR Compliance - Privacy requirements
Frequently Asked Questions
How does LogTide detect security threats like brute force attacks?
LogTide applies detection rules to incoming log streams in real time, so when your application logs failed login events with source IP, a rule that counts login_failed events grouped by IP over a five-minute window will fire an alert as soon as the threshold is crossed. Built-in Sigma rule support means you can also import community-maintained threat detection rules without writing custom logic.
Is LogTide a viable open-source SIEM alternative?
LogTide provides core SIEM capabilities including real-time threat detection, Sigma rule support, structured audit trails, and immediate alerting to Slack or PagerDuty, at a fraction of the cost of traditional SIEM solutions. Because it is self-hosted, your security logs never leave your own infrastructure.
What types of security events can LogTide monitor?
LogTide can monitor authentication events such as login successes and failures, brute force and impossible travel detection, privilege escalation via role changes, API abuse through rate limit violations, SQL injection and XSS attempts, and off-hours admin activity. The structured security logger pattern described in this guide standardises severity levels across all these event types.
How does LogTide support compliance audit trails for SOC2 or ISO27001?
LogTide stores every security and audit event as a structured, timestamped log record covering who performed an action, what resource was affected, where the request originated, and whether it succeeded. You can configure retention policies per event type, with security events recommended at a minimum of one year, making LogTide suitable as the audit log backbone for compliance frameworks such as SOC2 and ISO27001.