LintLiot detects impossible travel, credential stuffing, data exfiltration, and API abuse. No rules to write — call recordAuthFailure and recordDataRead in your handlers.
Use this file to discover all available pages before exploring further.
Individual request inspection catches single malicious requests. Anomaly detection catches the patterns that emerge across many requests over time: a user account accessed from two continents within minutes, a script cycling through thousands of email/password combinations, a user account suddenly pulling 50,000 database records in a 5-minute window. LintLiot’s anomaly detector runs continuously in-process, correlating events across requests to find threats that per-request WAF rules would miss.
The same user account authenticates from two geographic locations that cannot be reached in the elapsed time at any commercially available transport speed.
Brute force
A single IP address makes repeated failed login attempts against any account within a rolling time window.
Credential stuffing
Multiple unique IP addresses all target the same user account with failed login attempts — the pattern of automated credential list attacks.
Data exfiltration
A user reads an abnormally large volume of records compared to their historical baseline, suggesting bulk data extraction.
API abuse
An API key or user account makes requests at an inhuman rate — far beyond what a real user could generate manually.
Scraping
An IP address systematically hits a large number of unique endpoints in a short window, suggesting automated content harvesting.
Account takeover
A login from a new device or country that doesn’t match the user’s established session fingerprint.
Impossible travel uses the haversine formula to calculate the great-circle distance between two login coordinates, then divides by the elapsed time to get an implied speed. If that speed exceeds 900 km/h — the maximum speed of commercial aviation — the second login is flagged.
// How it works internallyconst distanceKm = haversineDistanceKm(prevLat, prevLon, currentLat, currentLon)const elapsedHours = elapsedMs / 3_600_000const impliedSpeedKmh = distanceKm / elapsedHoursif (impliedSpeedKmh > 900) { // Impossible travel detected // Second session invalidated automatically // Event logged as auth.impossible_travel at critical severity}
Two safety margins prevent false positives:
Logins less than 5 minutes apart are ignored (same session reconnect is plausible)
Distances under 100 km are ignored (regional IP geolocation variance)
When impossible travel is detected, the second session is invalidated and a critical alert fires immediately. You don’t need to handle this in your code — LintLiot handles it in the middleware layer.
Call lintliot.anomaly.recordAuthFailure() in your login route whenever authentication fails. This feeds both brute force and credential stuffing detection:
// In your login handlerasync function loginHandler(req, res) { const { email, password } = req.body const user = await findUserByEmail(email) const valid = user && await verifyPassword(password, user.passwordHash) if (!valid) { // Pass the request and the target userId (if you found one) const anomaly = await lintliot.anomaly.recordAuthFailure(req, user?.id) if (anomaly.detected) { console.log(`Anomaly: ${anomaly.type}`) // anomaly.type === 'brute_force' or 'credential_stuffing' // anomaly.severity === 'HIGH' or 'CRITICAL' // anomaly.autoBlock === true means the IP was already blocked } return res.status(401).json({ error: 'Invalid credentials' }) } // ... continue with successful login}
Call lintliot.anomaly.recordDataRead() after any query that returns records to track data access volume. LintLiot accumulates totals per user in a rolling 5-minute window and alerts when the volume exceeds 500 records (or your configured threshold):
// After a database queryconst { data: orders } = await supabase .from('orders') .select('*') .eq('user_id', userId)// Tell LintLiot how many records this user just readawait lintliot.anomaly.recordDataRead(userId, orders.length)// Or instrument it inlineconst anomaly = await lintliot.anomaly.recordDataRead(userId, orders.length)if (anomaly.detected) { // anomaly.type === 'data_exfiltration' // Log, alert, or throttle the response}
You don’t need to call recordDataRead on every single query. Focus on endpoints that return lists of records — user lists, export endpoints, search results, and report queries. Single-record reads rarely trigger the exfiltration threshold.
Returns an AnomalyResult with detected: true from the function you called
Pushes a threat.detected event to the LintLiot dashboard in real time
Fires a push notification to the dashboard (critical events are immediate)
For some anomaly types, autoBlock is set to true — this means LintLiot has already blocked the source IP or invalidated the session at the middleware layer. For others (like credential stuffing), blocking is flagged as recommended but left to your discretion, since blocking all source IPs might collaterally block legitimate users on shared networks.
Anomaly detection is stateful — state is stored in-process per SDK instance. In multi-instance deployments (serverless, horizontal scale), each instance maintains its own counters. For cross-instance correlation, LintLiot pushes events to the central API where the dashboard performs cross-instance analysis. Per-instance thresholds may be lower than they appear in a multi-instance deployment.
To prevent alert fatigue, LintLiot suppresses repeated alerts for the same active anomaly. Once an anomaly fires an alert, it won’t fire again for the same IP or user for 15 minutes, unless the underlying condition escalates in severity.