Sprzedawca nie musi kliknąć w złego linka, żeby stracić dostęp do konta. Wystarczy, że uwierzy w wiadomość, która wygląda, jakby przyszła od marketplace'u.
W Artovnii komunikacja klient–sprzedawca idzie przez wbudowany moduł wiadomości: kupujący pisze do sprzedawcy przez formularz kontaktowy, sprzedawca dostaje powiadomienie mailem i odpowiada z poziomu panelu. Formularz jest dostępny wyłącznie dla zarejestrowanych i zalogowanych użytkowników — to nie jest anonimowy kanał otwarty dla każdego. Mimo to okazał się wystarczająco wygodny dla kogoś, kto nie miał zamiaru kupić rękodzieła. Chciał danych logowania sprzedawcy.
Reakcja: najpierw zatrzymać, potem naprawiać
Zero eleganckich rozwiązań na start. Zakomentowałem linię rejestrującą moduł w medusa-config.ts:
// { resolve: './src/modules/messaging' },...i wypchnąłem to na produkcję. Cały moduł wiadomości poszedł offline, zanim zdążyłem w pełni zrozumieć skalę problemu. Równolegle konto atakującego zostało zbanowane i usunięte tego samego dnia — z tego konkretnego konta nie było już możliwości ponowienia próby. To była świadoma kolejność: wyłączenie kanału zawsze wygrywa z analizą w locie, gdy w grę wchodzi realne ryzyko, że kolejny sprzedawca dostanie tę samą wiadomość i tym razem w nią uwierzy. Strata na czasie odpowiedzi klient–sprzedawca była akceptowalna. Strata danych logowania sprzedawcy — nie.
Dopiero po wyłączeniu modułu zacząłem pracować nad tym, żeby ta sama klasa ataku nie miała już szans wrócić — a sam moduł miał wrócić online dopiero razem z tymi zabezpieczeniami, nie wcześniej.
Wysyłany został również email z informacją do wszystkich sprzedawców poprzez wewnętrzny moduł newslettera, ostrzegający o zdarzeniu oraz instrukcjach postępowania.
To nie jest tylko filtr na linki
Najprostsze podejście do phishingu to blokowanie wiadomości z linkami spoza domeny. Problem w tym, że skuteczny atak socjotechniczny często nie potrzebuje żadnego linka. Wystarczy podszyć się pod support, zbudować presję czasową i poprosić o dane logowania albo "weryfikację płatności" wprost w treści wiadomości.
Dlatego walidacja w Artovnii patrzy na dwa niezależne sygnały:
- linki spoza
artovnia.comi jej subdomen, - język podszywający się pod support / administrację Artovnii, połączony z prośbą o dane, presją albo fałszywym scenariuszem płatności.
Wystarczy, że zadziała jeden z nich, a wiadomość jest blokowana, zanim trafi do bazy albo na skrzynkę sprzedawcy.
Ważony scoring zamiast jednego słowa-wyzwalacza
Blokowanie po pojedynczym słowie jest kruche — albo przepuszcza sprytnie sformułowany atak, albo blokuje niewinną wiadomość klienta, który akurat wspomniał "bank" w zupełnie innym kontekście. Zamiast tego każda wiadomość dostaje punktację złożoną z kilku kategorii sygnału: podszywanie się pod markę, presja czasowa, prośba o dane logowania, dane finansowe, próba przeniesienia rozmowy poza platformę, fałszywe instytucje, fałszywe scenariusze zwrotu płatności.
Uproszczona ilustracja mechanizmu — celowo bez pełnej listy fraz i wag, bo to akurat jeden z tych elementów, którego nie warto publikować co do joty:
type SignalCategory =
| 'impersonation'
| 'urgency'
| 'credentials'
| 'financial_data'
| 'off_platform_contact'
| 'fake_institution'
| 'fake_scenario'
function scoreMessage(subject: string, content: string): number {
const text = `${subject} ${content}`.toLowerCase()
return matchedPatterns(text).reduce(
(score, pattern) => score + pattern.weight,
0
)
}
function validateCustomerMessageSafety(
senderType: MessageSender,
subject: string,
content: string
) {
if (senderType !== MessageSender.USER) return
const hasExternalLink = extractExternalMessageUrls(content).length > 0
const riskScore = scoreMessage(subject, content)
if (hasExternalLink || riskScore >= RISK_THRESHOLD) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
'Wiadomość zawiera treści niedozwolone ze względów bezpieczeństwa.'
)
}
}Jedna mocna fraza — na przykład wprost proszona o hasło — potrafi zablokować wiadomość samodzielnie. Kilka słabszych sygnałów musi się złożyć, zanim próg zostanie przekroczony. To pozwala złapać wiadomości, które nie zawierają żadnego linka, ale wprost proszą o "dane logowania, żeby zweryfikować płatność".
Subject i treść liczą się razem
Wygodny sposób na obejście filtra treści to schowanie niebezpiecznej prośby w temacie wiadomości, a nie w jej treści — w nadziei, że system sprawdza tylko content. Walidacja w Artovnii łączy subject + content w jeden ciąg przed oceną, więc temat w stylu "weryfikacja konta" z pustą treścią i tak zostaje oceniony.
Blokada na wejściu, nie sprzątanie po fakcie
Gdy wiadomość klienta przekroczy próg ryzyka, API zwraca błąd NOT_ALLOWED i nic nie trafia do bazy. Wątek nie powstaje, sprzedawca nie dostaje maila, treść nigdy nie zostaje zapisana. To był świadomy wybór — moderacja post factum oznacza, że przez jakiś czas złośliwa wiadomość i tak leży w skrzynce sprzedawcy, zanim ktoś ją usunie.
Redakcja linków tam, gdzie i tak by się nie przydały
Nawet legalna, ale zewnętrzna wiadomość klienta trafiająca do sprzedawcy nie powinna zawierać klikalnego linka spoza platformy w mailu z powiadomieniem. Zamiast usuwać całą wiadomość, treść e-maila do sprzedawcy zamienia zewnętrzne adresy na komunikat [link zewnętrzny ukryty ze względów bezpieczeństwa] — a na dole każdej wiadomości od klienta pojawia się stałe przypomnienie, że Artovnia nigdy nie prosi sprzedawców o dane logowania czy płatności przez wiadomość od klienta.
Do tego doszedł jeszcze jeden, mniej oczywisty wektor: nic nie stoi na przeszkodzie, żeby klient wpisał w polu nadawcy "Artovnia Support" albo "Administracja". Nazwy zarezerwowane — artovnia, support, wsparcie, administrator, administracja, admin — są teraz wykrywane i zastępowane neutralnym "Klient Artovnia" w nagłówku maila do sprzedawcy. Wiadomości od prawdziwej administracji Artovnii tej reguły nie dotyczą — to zaufany nadawca.
Panel moderacji, gdy coś jednak prześlizgnie się przez próg
Automatyczna walidacja nie zastępuje możliwości ręcznej reakcji. W adminie pojawiła się nowa zakładka Wiadomości z listą wszystkich wątków, filtrami i akcją oznaczenia wątku jako spam. Oznaczenie jest miękkie — wątek znika z widoku sprzedawcy i klienta, ale rekord zostaje w bazie i można go przywrócić jednym kliknięciem, gdyby moderacja pomyliła się co do intencji.
Czy takie zabezpieczenia nie powinny być tam od samego początku?
To pytanie, które sam sobie zadałem po fakcie — i odpowiedź brzmi: nie, i to jest normalne. Trust & safety w komunikacji klient–sprzedawca prawie zawsze powstaje jako reakcja na realny atak, nie jako założenie projektowe od pierwszego dnia. Dotyczy to nie tylko małych marketplace'ów.
Airbnb przez pierwsze trzy lata działania nie miało dedykowanego zespołu bezpieczeństwa. Dopiero głośny incydent w 2011 roku, w którym gospodyni z San Francisco wróciła do splądrowanego mieszkania, doprowadził do stworzenia całego działu trust & safety, 24-godzinnej linii wsparcia i gwarancji dla hostów. Upwork i Fiverr do dziś aktywnie wykrywają próby przenoszenia rozmowy poza platformę — to dokładnie ta sama kategoria sygnału, którą mam w swoim scoringu jako "kontakt poza platformą" — i ten mechanizm też powstał jako odpowiedź na realne nadużycia, a nie jako gotowy element architektury od startu.
Powód jest praktyczny: nie da się sensownie skalibrować progów i kategorii sygnału bez danych o tym, jak faktycznie wygląda atak na konkretną platformę. Zanim nie ma incydentu, nie ma punktu odniesienia. Dlatego dojrzała architektura tego typu — reguły, potem scoring, potem panel moderacji, potem logowanie — buduje się warstwami, w miarę jak platforma rośnie i staje się realnym celem. Reakcja w Artovnii różniła się od tego wzorca jednym: czasem. Zamiast lat, cała sekwencja zajęła kilka dni.
Czego jeszcze nie zrobiłem
- Brak logowania prób ataku. Ten konkretny atakujący został zbanowany i usunięty od razu, więc z tego kierunku nie ma już ryzyka. Ale bez logowania (ID nadawcy, IP, dopasowanych sygnałów) trudniej rozpoznać kogoś, kto założy nowe konto i spróbuje ponownie. To następny krok, potrzebny do wykrywania powtarzających się prób na szerszą skalę.
- Reguły są po polsku. Kategorie sygnału są uniwersalne, ale konkretne wzorce dopasowane są pod polskojęzyczny phishing kierowany do polskich sprzedawców. Inny język komunikacji klient–sprzedawca wymagałby własnego zestawu wzorców.
- Próg to kompromis, nie pewność. Rzadki, ale możliwy false positive: klient, który w niewinnym pytaniu wspomni "bank" albo "dostawę kurierem", może otrzeć się o próg. Lepsze to niż przepuszczona próba wyłudzenia danych logowania.
- Rate-limit na razie tylko per użytkownik. Limiter per IP, obok istniejącego limitera in-memory na użytkownika, jest w planach.
Dlaczego to ważne dla całego marketplace'u, nie tylko dla jednej wiadomości
Sprzedawca w marketplace ufa platformie w innym sensie niż klient ufa sprzedawcy. Klient ufa jednemu sprzedawcy przy jednej transakcji. Sprzedawca oddaje platformie dostęp do panelu, danych finansowych i komunikacji z własnymi klientami — na stałe, nie przy jednej transakcji. Jeśli kanał komunikacji klient–sprzedawca może posłużyć do wyłudzenia tego dostępu, to nie jest incydent dotyczący jednej wiadomości. To pytanie o to, czy sprzedawcy mogą bezpiecznie prowadzić biznes na platformie w ogóle.
Dlatego reakcja nie skończyła się na "zablokuj tę jedną wiadomość". Skończyła się na architekturze, która blokuje całą klasę ataku — niezależnie od tego, przez który endpoint albo serwis wiadomość próbuje przejść.
Zakończenie
Moduł wrócił online dopiero teraz, z całym tym zestawem zabezpieczeń na pokładzie — nie wcześniej. Konto atakującego zostało zbanowane i usunięte tego samego dnia, więc z tego konkretnego kierunku zagrożenie jest zamknięte.
Nie mam złudzeń, że to była ostatnia próba tego typu. Ktoś spróbuje czegoś innego, prędzej czy później — tak to działa. Różnica jest taka, że następnym razem trzeba będzie ominąć dwa niezależne sygnały zamiast jednego filtra na linki, a jeśli mimo wszystko coś się prześlizgnie, dowiem się o tym z panelu moderacji, a nie ze zrzutu ekranu od zdenerwowanego sprzedawcy.


