Suspicious activity triggers additional step-up
Why it matters
Impossible travel, rapid transaction sequences, and unusually large transfer amounts are established signals of account compromise or fraud. Without anomaly detection, an attacker who obtains a valid session token — through phishing, session fixation, or XSS — can execute transactions that would immediately appear suspicious to any human reviewer but proceed unchallenged by the application. CWE-307 (Improper Restriction of Excessive Authentication Attempts) and NIST SI-4 (System Monitoring) address active threat detection requirements. PCI-DSS Req 10.7 requires that anomalies trigger alerts. Suspicious activity step-up provides a dynamic second factor that activates precisely when risk is highest, without adding friction to normal user behavior.
Severity rationale
Low because suspicious activity detection is a defense-in-depth layer — its absence does not enable initial access, but it removes the runtime signal that would detect and interrupt an active attack on an already-compromised session.
Remediation
Add at least two anomaly detection rules in src/lib/fraudDetection.ts — impossible travel is the highest-signal, lowest-false-positive starting point:
export async function checkSuspiciousActivity(
userId: string,
currentLocation: { lat: number; lng: number }
): Promise<boolean> {
const lastSession = await db.sessions.findFirst({
where: { userId },
orderBy: { loginAt: 'desc' },
skip: 1 // skip current
});
if (!lastSession?.location) return false;
const elapsedMin =
(Date.now() - lastSession.loginAt.getTime()) / 60000;
const distanceKm = geoDistance(lastSession.location, currentLocation);
const speedKmMin = distanceKm / elapsedMin;
return speedKmMin > 15; // >15 km/min is physically impossible
}
When checkSuspiciousActivity returns true, call requireStepUp() before allowing any financial operation to proceed. Add a second rule (e.g., transactions above a threshold amount) to meet the two-rule minimum.
Detection
- ID:
suspicious-activity-step-up - Severity:
low - What to look for: Count all anomaly detection rules (impossible travel, unusual amounts, rapid sequences, failed login threshold). Enumerate each rule and its trigger threshold. Quote the actual detection logic found. Verify at least 2 suspicious activity patterns are monitored. Count all step-up triggers for suspicious activity.
- Pass criteria: At least 2 suspicious activity patterns are detected (e.g., impossible travel, unusual transaction amount). When detected, at least 1 step-up challenge is triggered. Report the count even on pass (e.g., "3 detection rules: impossible travel (>15 km/min), amount >$10000, >5 failed logins — all trigger step-up").
- Fail criteria: No suspicious activity detection (0 rules), or detection exists but does not trigger step-up (0 step-up triggers for anomalies).
- Skip (N/A) when: The application does not perform financial transactions — cite the actual project type found.
- Detail on fail:
"0 anomaly detection rules — impossible-travel logins not detected, no fraud monitoring" - Remediation: Add simple anomaly detection (in
src/lib/fraudDetection.ts):// lib/fraudDetection.ts export async function checkSuspiciousActivity(userId: string, action: string) { const recentSessions = await db.sessions.findMany({ where: { userId, expiresAt: { gt: new Date() } }, orderBy: { loginAt: 'desc' }, take: 1 }); const lastSession = recentSessions[0]; if (!lastSession) return false; // First login, not suspicious const timeSinceLastLogin = (Date.now() - lastSession.loginAt.getTime()) / 1000 / 60; // minutes const distance = geoDistance(lastSession.location, currentLocation); // km const speedOfTravel = distance / timeSinceLastLogin; // km/min if (speedOfTravel > 15) { // Impossible travel (>15 km/min) return true; // Flag as suspicious } return false; }
External references
- cwe · CWE-307 — Improper Restriction of Excessive Authentication Attempts
- owasp:2021 · A07
- nist:rev5 · SI-4 — System Monitoring
- pci-dss:4.0 · Req 10.7 — Failures of critical security controls detected and reported
Taxons
History
- 2026-04-18·v1.0.0·Initial import from finserv-session-security·automated