Przejdź do treści
Intum Dev

Jak zabezpieczyć SaaS przed spamem i fraudem - praktyczny przewodnik

Aktualizacja: 8 min czytania

Tworzysz nowy SaaS i widzisz pierwsze konta, które wyglądają na fake - albo masz już produkcję, a spammerzy zaczęli puchnąć. Poniżej kompletny przewodnik: jakie sygnały zbierać, jak liczyć trust score, jakie techniki obronne mają sens, oraz jak to wszystko utrzymać w produkcji bez zalania moderacji ticketami. Konkretni providerzy są w osobnym wpisie Anti-fraud i CAPTCHA dla SaaSów.

W praktyce nie chodzi o jedno pole is_spam, tylko o zestaw sygnałów, z których budujesz score. Trzymaj je przy accounts lub w osobnej tabeli account_signals żeby główna nie puchła.

Sygnały rejestracji (zbierane raz, przy signupie)

Pole Skąd Po co
signup_ip, signup_country, signup_asn request ASN łapie sieci hostingowe (DigitalOcean, Hetzner) używane przez boty
signup_user_agent request Stare lub nietypowe UA = bot
signup_device_fingerprint FingerprintJS / ThumbmarkJS Jedna farma kont = ten sam fingerprint
signup_is_vpn / proxy / tor / datacenter IPQualityScore, MaxMind, AbuseIPDB VPN + disposable email = 90% spam
email_domain, email_is_disposable, email_is_freemail, email_mx_valid własna logika + lista ivolo Mailinator/tempmail to czerwona flaga
email_verified_at klik w link Najtańszy filtr
captcha_score Turnstile / reCAPTCHA v3 0.0-1.0, niski score = automat
referrer, utm_source request Boty mają puste albo identyczne UTM-y
signup_fill_time_ms timer JS Bot wypełni 200ms, człowiek 15s
honeypot_triggered ukryte pole Tylko bot wypełni pole z display: none

Sygnały tożsamości (wzmacniają zaufanie)

  • weryfikacja telefonu - data, kraj, typ carriera (mobile vs VOIP). Numery z Twilio i Google Voice są podejrzane
  • weryfikacja płatności - data, kraj billingu, ostatnie 4 cyfry karty
  • billing_country_mismatch - bool, kraj karty inny niż signup/IP

Sygnały behawioralne (aktualizowane na bieżąco)

  • last_login_at, login_count, failed_login_count
  • unique_ips_count_30d - konto z 8 krajów w tygodniu zwykle skradzione albo współdzielone
  • unique_devices_count, unique_countries_count_30d
  • actions_per_hour_max - bot wali stałym tempem, człowiek ma piki
  • api_calls_count, api_error_rate - wysoki error rate to skanowanie albo scraping

Sygnały reputacji w produkcie

  • content_flagged_count - ile razy treści użytkownika oflagowano
  • reports_received_count - zgłoszenia od innych userów
  • outbound_emails_bounce_rate - spammer zbierający adresy ma 60% bounce
  • invites_sent_count i invites_accepted_ratio - asymetria zdradza spam

Pola decyzyjne (tych używa aplikacja)

Pole Typ Do czego
status enum: pending_verification, active, restricted, shadow_banned, suspended, banned Centralne pole sterujące
trust_score int 0-100 Ważona suma sygnałów, liczona w jobie cyklicznie
risk_flags JSONB albo bitmask disposable_email, vpn, velocity_abuse itp.
rate_limit_tier enum/int Niskozaufane konta dostają niższe limity automatycznie
manual_review_status, reviewed_by, reviewed_at   Audit ręcznych decyzji
suspended_at, suspended_reason   Historia banów

Techniki, których same kolumny nie załatwią

Progressive trust - nowe konto może mało: limit zaproszeń, brak publicznych linków, brak webhooków na zewnątrz. Zaufanie rośnie z czasem i z weryfikacjami.

Shadow ban - spammer widzi swoje treści jako opublikowane, ale inni userzy ich nie widzą. Nie próbuje obchodzić bana, bo nie wie, że jest banem. Implementacja w Rails: default_scope { where(shadow_banned: false) } na publicznych scope’ach, ale bez tego w widoku samego autora.

Velocity rules - reguła “więcej niż X kont z tego ASN w 1h” albo “więcej niż X zaproszeń z konta w 10 min” przełącza automatycznie na restricted. Najprostsza implementacja w Redisie z TTL.

Honeypoty na formularzach - ukryte pole website albo phone2, które tylko bot wypełni. display: none + aria-hidden="true" + tabindex=-1.

Device/browser fingerprinting - to samo fingerprint na 50 kontach = farma. FingerprintJS w wersji OSS daje 70% wartości za darmo.

Email reputation - sprawdzaj wiek domeny, rekord SPF/DMARC, czy domena nie jest tylko parkowana. Domena z whois <30 dni + brak DMARC = czerwona flaga.

Łańcuch weryfikacji - email -> telefon -> karta. Każdy krok odsiewa kolejną warstwę spamu.

Asynchroniczny scoring - po signupie wrzucasz job do kolejki, który dopytuje IPQualityScore/Sift/Castle i aktualizuje trust_score. Nie blokujesz UX rejestracji.

Analiza grafowa - najmocniejsza broń przeciw farmom

Pojedyncze konto może wyglądać czysto, ale farma 200 kont zdradzi się w relacjach. Buduj graf i licz cechy klastra:

  • wspólny fingerprint / IP / ASN / payment_fingerprint to krawędź między kontami
  • connected components - jeśli komponent ma >N kont założonych w <M dni, automatyczny review całego klastra
  • invite chain - kto kogo zaprosił. Spammerzy często masowo zapraszają się nawzajem żeby podbić sztuczny “trust”
  • wspólne treści - hash/simhash treści, te same linki w opisach, ten sam numer telefonu w profilach

W praktyce nocny job przelatuje grafem, oznacza klastry, a trust_score konta dziedziczy karę z klastra. Próg ważny - pojedyncze połączenie nic nie znaczy, 5 wspólnych sygnałów już tak.

Feedback loop - bez tego system gnije

Każda ręczna decyzja moderatora (ban / unban / “to był false positive”) MUSI wracać do systemu jako label:

# tabela account_decisions
account_id, decision, reason, decided_by, decided_at, signals_snapshot (JSONB)

Snapshot sygnałów w momencie decyzji - żeby móc potem trenować model na realnych danych. Raz na tydzień regresja logistyczna na tych labelach generuje nowe wagi do trust_score. Bez tego wagi są na czuja i z czasem rozjeżdżają się z rzeczywistością.

Na start nie potrzebujesz ML, wystarczy ważona suma:

disposable_email:  -30
vpn_signup:        -15
phone_verified:    +20
payment_verified:  +30
account_age_30d:   +10

ML dokładasz dopiero gdy masz >1000 oznaczonych decyzji.

Appeal / odwołania

To, czego nikt nie robi, a powinien.

  • zbanowany user dostaje formularz odwołania, nawet jeśli to bot. Po pierwsze koszt obsługi mały, po drugie masz dane do feedback loop
  • każde uznane odwołanie to silny sygnał false-positive
  • bez tego średnio raz na kwartał banujesz cudzoziemca z VPN-em z kawiarni i masz publiczny dramat na X

Metryki, które musisz widzieć codziennie

Metryka Po co
Signup funnel by trust tier Ilu w pending, active, restricted per dzień
Time-to-detect Minuty od signupu do banu - im krócej tym lepiej
False positive rate % unbanowań po odwołaniu
Spam reach Ile treści od konta dotarło do innych zanim trafiło na shadow ban
Top 20 ASN signupów Nagły skok z jednego ASN = atak w toku

Bez dashboardu nie wiesz, czy system działa, czy tylko miele.

Trzymanie IP, fingerprintu i geo to dane osobowe.

  • podstawa prawna: prawnie uzasadniony interes (Art. 6.1.f). Security to klasyk, ale udokumentuj ocenę interesów
  • retencja: trust signals max 12-24 mies. od ostatniej aktywności. Zrób cron czyszczący
  • polityka prywatności musi wymieniać IPQualityScore/Sift jako sub-procesora
  • prawo dostępu - user może poprosić o swoje sygnały, przygotuj export
  • profilowanie automatyczne (Art. 22) - automatyczny hard ban bez człowieka jest formalnie problematyczny. Bezpieczniej: restricted automatycznie + ban dopiero po review przez moderatora

Edge case’y, które zawsze wracają

  • korporacyjne IP - 500 userów z jednego NAT-u wygląda jak farma. Whitelistuj znane firmowe ASN-y
  • mobile carrier-grade NAT - operatorzy komórkowi dzielą jedno IP między tysiące userów
  • iCloud Private Relay i Apple Hide My Email - wygląda jak VPN + disposable email, a to legit Apple
  • studenci i akademiki - jeden budynek = jeden IP, dużo kont
  • powracający user po latach - stare konto + nowe urządzenie + VPN z urlopu = fałszywy alarm

Dla każdego z tych przypadków potrzebujesz allow-listy albo reguły “human review zamiast auto-ban”.

Wzorce czysto Rails-owe

  • Scoring jako job: TrustScoreRecalculationJob.perform_later(account_id) triggerowany z after_create_commit na Account oraz cyklicznie z crona raz dziennie dla aktywnych
  • kolumna trust_score cache’owana w accounts, ale liczona z account_signals. Nie licz on-the-fly w każdym żądaniu
  • concern Trustable na modelach robiących ryzykowne akcje (wysyłka emaila, publikacja publiczna, zaproszenia) sprawdza account.trust_score >= threshold zanim odpali
  • Rate limiter per tier: Rack::Attack albo własny w Redisie. Klucz "rate:#{account_id}:#{action}", limit czytany z account.rate_limit_tier
  • shadow ban przez scope - default_scope na publicznych widokach Postów/Komentarzy, ale bez tego w widoku samego autora (autor widzi swoje treści, reszta nie)

Co najczęściej idzie źle

  1. Za agresywny start - banujesz 5% legit userów, support tonie w ticketach, biznes każe wyłączyć system. Zaczynaj od shadow ban i monitoringu, hard bany dopiero po tygodniach kalibracji
  2. Brak shadow ban - spammer widzi 403, zmienia IP/email, wraca w 5 min. Shadow ban kupuje ci dni
  3. Hardcoded thresholds bez A/B - nie wiesz, czy trust_score < 30 = restricted jest lepsze niż < 40
  4. Sygnały bez TTL - konto z 2019 ma flagę vpn_signup i nadal jest karane, choć od 5 lat loguje się normalnie. Każdy sygnał musi mieć datę i wagę malejącą z czasem
  5. Brak audit logu decyzji - moderator banuje, nie wiadomo dlaczego, user się odwołuje, nikt nie potrafi odtworzyć

MVP - kolejność wdrożenia

  1. Cloudflare Turnstile + email verification + lista disposable - odsiewa 70% śmieci za darmo
  2. Tabela account_signals (JSONB) + trust_score (int) + status enum w accounts
  3. Async job po signupie: IPQualityScore lookup -> zapis sygnałów -> przeliczenie score
  4. Shadow ban dla trust_score < 20 zamiast hard ban
  5. Prosty dashboard moderacyjny + appeal form
  6. Feedback loop - zapis decyzji moderatorów ze snapshotem sygnałów
  7. Po 1-2 mies. danych dopiero wtedy graf klastrów i ewentualny ML

Reszta (carrier check, device fingerprint farm detection, behavioral biometrics) wchodzi dopiero gdy zobaczysz konkretny wektor ataku, którego MVP nie łapie. Najtańsze 80% wartości daje pierwsze 4 punkty z listy.

Czy ten wpis był pomocny?

Udostępnij

Komentarze