[Intum Dev](https://intum.dev.md) / [Technologia](https://intum.dev/technologia.md)

# [Jak zabezpieczyć SaaS przed spamem i fraudem - praktyczny przewodnik](https://intum.dev/technologia/jak-zabezpiecyc-saas-przed-spamem-i-fraudem-praktyczny-przewodnik.md)

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](../technologia/anti-fraud-i-captcha-dla-saasow-przeglad-providerow).

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.

## RODO / legal (PL i UE)

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.

---

## Powiązane

- [Anti-fraud i CAPTCHA dla SaaSów - przegląd providerów](https://intum.dev/technologia/anti-fraud-i-captcha-dla-saasow-przeglad-providerow.md)
