Skip to main content

Anomaly detection

WIP

Hijack / device-fingerprint / IP-shift detectors that gate session use; aggregated into a single allow / step-up / deny decision.

What this is

AuthEngine.anomaly is a pluggable detector chain. Every call to resolveSession(req, { requestSnapshot }) runs the registered detectors and produces a single decision: allow, step-up, or deny.

Detectors are independent functions; the aggregator combines their outputs. Operators branch on the final field rather than re-running every detector at every route.

Built-in detectors

DetectorWhat it looks atWhen it fires
ipShiftSession ip vs request snapshot IPSudden country / ASN change
userAgentShiftSession userAgent vs request UAUA family changed mid-session
fingerprintSession fingerprint vs requestCanvas / WebGL / font hash mismatch
replayCookie reuse from impossible-distance second requestStolen-cookie indicator

Each detector returns { score: 0..1, signals: [...] }. The aggregator sums and maps:

  • score < 0.3 -> allow
  • 0.3 <= score < 0.7 -> step-up (force re-auth via MFA)
  • score >= 0.7 -> deny (revoke session)

Registration

auth.anomaly.register(ipShift({ allowedAsnDelta: 1 }))
auth.anomaly.register(userAgentShift())
auth.anomaly.register(fingerprint({ hashAlg: 'sha256' }))

Snapshots are captured per-request and passed in:

const resolved = await auth.resolveSession(req, {
  requestSnapshot: {
    ip: req.ip,
    userAgent: req.get('user-agent') ?? '',
    fingerprint: req.cookies['fp'] ?? '',
  },
})

if (resolved?.anomaly?.decision === 'deny') {
  await auth.sessions.revoke(resolved.session.id)
  return res.status(401).end()
}
if (resolved?.anomaly?.decision === 'step-up') {
  return res.redirect('/auth/mfa?reason=anomaly')
}

Custom detectors

auth.anomaly.register({
  id: 'tor-exit',
  evaluate: async ({ session, req }) => {
    const onTorList = await checkTorBlocklist(req.ip)
    return {
      score: onTorList ? 0.9 : 0,
      signals: onTorList ? ['tor-exit'] : [],
    }
  },
})

Detector throws are caught by the aggregator; a misbehaving detector cannot wedge the session.