Skip to content


Prevent Account Takeover

event login
verdict := Decision(Allow, Block default Allow)

user := $.user as string            -- user login name
ip := $.ip as string                -- client IP address
successful := $.successful as bool  -- was the password entered correctly?

-- How many accounts has this IP attempted to log into recently?
attempts_per_ip := CountUnique(user by ip last 2 hours)
-- How many of those accounts did the IP log into successfully?
successes_per_ip := CountUnique(user by ip where successful last 2 hours)
-- The difference is the number of consistently failed accounts
failures_per_ip := attempts_per_ip - successes_per_ip
-- Compute the failure rate
failure_rate_ip := failures_per_ip / attempts_per_ip

-- Tag as credential stuffing when number of failures and failure rate are high
credential_stuffing := failures_per_ip > 10 and failure_rate_ip > 0.8
-- Block the attacker from logging in
successful_attack := Block when successful and credential_stuffing

Identify Spam with Simhash

event create_comment

body := $.comment.body as string -- Comment text body

-- Apply Simhash algorithm to create a fingerprint of comment text
simhash := Simhash(body)
-- How many similar comments did we see in the last week?
sim_per_week := Count(by simhash last week)
-- How many similar comments did we see in the last day?
sim_per_day := Count(by simhash last day)

-- Tag as spam if rate of similar comments is accelerating
spam := sim_per_day > 20 and sim_per_day / sim_per_week > 0.9

Scrutinize Posts Based On Signup Behavior

event signup

email := $.email as string   -- Email at signup time
ip := $.ip as string         -- IP at signup time

-- How many signups from this IP?
per_ip := Count(by ip last 1.5 hours)

event create_post
verdict := Decision(Allow, Review default Allow)

email := $.email as string     -- Email at time of post
body := $.post.body as string  -- Post text body

-- Did this user sign up within the last week on a high-velocity IP?
suspicious_signup := LatestTime<signup>(by email where per_ip > 5 last week)
-- How many posts has this user created recently?
per_email := Count(by email last day)

-- Tag posts from accounts with suspicious signups
from_suspicious_signup := suspicious_signup is not null
-- Review repeat posts from suspicious accounts
repeat_suspicious := Review when per_email > 1 and from_suspicious_signup

Flag Shopify Orders by Geolocation

event order
verdict := Decision(Allow, Block, Review default Allow)

-- Extract order, transaction, and payment details
avs_result := $.transaction.payment_details.avs_result_code as string
bill_lat := $.payment.billing_address.latitude as float
bill_lng := $.payment.billing_address.longitude as float
ship_lat := $.shipping_address.latitude as float
bill_lng := $.shipping_address.longitude as float
ip := $.browser_ip as string

-- Enrich IP with latitude / longitude data
ip_lat, ip_lng := IPLocate(ip)

-- Distance from billing to shipping
bill_ship_miles := GeoMiles(bill_lat, bill_lng, ship_lat, ship_lng)
-- Distance from billing to IP
bill_ip_miles := GeoMiles(bill_lat, bill_lng, ip_lat, ip_lng)
-- Distance from shipping to IP
ship_ip_miles := GeoMiles(ship_lat, ship_lng, ip_lat, ip_lng)
-- The shortest of the three distances
nearest_miles := Minimum(bill_ship_miles, bill_ip_miles, ship_ip_miles)
-- Does the AVS code indicate a zip code match?
avs_zip_match := avs_result in ['X', 'Y', 'W', 'Z']

-- Review if it is an AVS match and all locations are 50+ miles apart
far_apart := Review when avs_zip_match and nearest_miles > 50

Flag Shopify Orders by Name/Email Mismatch

event order
verdict := Decision(Allow, Block, Review default Allow)

-- Extract order details
email := $ as string
customer_create_time := $.order.customer.created_at as time
bill_first := $.order.billing_address.first_name as string
bill_last := $.order.billing_address.last_name as string
ship_first := $.order.shipping_address.first_name as string
ship_last := $.order.shipping_address.last_name as string

-- Customer account age in days
customer_tenure_days := Days(EventTime() - customer_create_time)

-- Extract the handle portion of the email address
handle := EmailHandle(email)
-- Lowercase and remove everything but letters from name and email
handle_clean := Lower(RemoveNonAlpha(handle))
first_clean := Lower(RemoveNonAlpha(bill_first))
last_clean := Lower(RemoveNonAlpha(bill_last))
-- Find similarity to firstlast
first_last := StringSimilarity(first_clean + last_clean, handle_clean)
-- Find similarity to lastfirst
last_first := StringSimilarity(last_clean + first_clean, handle_clean)
-- Choose the order that gives the best match score
similarity := Maximum(first_last, last_first)

-- A "new" account means it was created in the last two weeks
new_account := customer_tenure_days < 14
-- Billing-Shipping name match?
bill_ship_match := bill_first = ship_first and bill_last = ship_last
-- A similarity score of 0.5 is required to be a match
name_email_match := similarity >= 0.5

-- Review new accounts with B/S match and name/email mismatch
suspicious := Review when new_account and bill_ship_match and not name_email_match