·2 min read·← All posts
Go Security Anomaly Detection Fraud

Two signals

Most production fraud-detection stacks have hundreds of signals. Two of them produce most of the actual catches:

  1. Impossible travel. Login from Pune at 10:00 UTC, another from London at 10:15 UTC. 7,400 km in 15 minutes is impossible by aircraft, let alone in-flight WiFi. The session is compromised or shared. (A passkey-based authentication scheme eliminates credential theft but doesn't prevent session hijacking, so you still need this check.)

  2. Credential-stuffing density. A single IP making login attempts against many accounts in a short window. Either a stuffer trying leaked creds or a poorly-configured corporate proxy. Either way, throttle.

Impossible travel in Go

type Login struct {
    UserID    string
    Latitude  float64
    Longitude float64
    At        time.Time
}

func haversineKm(a, b Login) float64 {
    const R = 6371.0 // earth radius in km
    lat1, lat2 := a.Latitude*math.Pi/180, b.Latitude*math.Pi/180
    dLat := (b.Latitude - a.Latitude) * math.Pi / 180
    dLon := (b.Longitude - a.Longitude) * math.Pi / 180
    h := math.Sin(dLat/2)*math.Sin(dLat/2) +
         math.Cos(lat1)*math.Cos(lat2)*math.Sin(dLon/2)*math.Sin(dLon/2)
    return 2 * R * math.Asin(math.Sqrt(h))
}

func impossibleTravel(prev, curr Login, maxKmH float64) bool {
    distKm := haversineKm(prev, curr)
    hours := curr.At.Sub(prev.At).Hours()
    if hours <= 0 { return false }
    return distKm / hours > maxKmH
}

maxKmH set to 1000 (faster than any commercial flight) catches the obvious cases. This is part of the broader P2P lending fraud detection pipeline. Set to 200 for very paranoid configurations (catches train + car combos that are physically possible but unusual for the user’s pattern).

Credential-stuffing density

type StufferScore struct {
    AttemptsLastMinute int
    UniqueUsersLast5Min int
    FailureRateLast5Min float64
}

func suspicious(score StufferScore) bool {
    return score.AttemptsLastMinute > 30 ||
           score.UniqueUsersLast5Min > 10 ||
           score.FailureRateLast5Min > 0.7
}

Three thresholds. Tuned per workload. Tracked per IP in Redis with sliding-window counters.

Genie’s implementation

agents/cyber_guardian runs both checks plus a couple more (unknown device, fingerprint churn) on every login event. The output is a score 0-1; thresholds (configured per-tenant in the FREE-AI policy YAML) trigger:

The thresholds map to FREE-AI Rec 19 (Cybersecurity). Each block writes an Annexure VI incident with FailureMode = FailureUnintendedAction so the regulator can see the cumulative rate.

What this misses

Two attack classes need other signals:

For production, these two are the foundation. The advanced signals layer on top. Starting with these catches most volume; the long-tail attackers are a quarter-on-quarter investment.

← Back to all posts