Kod JavaScript działający w przeglądarce jest z definicji publiczny. Każdy może go pobrać, sformatować i przeczytać. Obfuskacja, czyli celowe zaciemnienie kodu, nie zmienia tego faktu, ale może znacząco utrudnić analizę i kopiowanie. W tym wpisie pokazuję, kiedy stosuję zaciemnianie kodu JavaScript, jak włączam je w procesie budowania, ile kosztuje to w wydajności i czego obfuskacja nie zabezpieczy, nawet przy najmocniejszych ustawieniach.
Kiedy obfuskacja ma sens, a kiedy nie
Zanim włączę zaciemnianie, odpowiadam sobie na pytanie, co dokładnie chcę chronić. Odpowiedź często pokazuje, że problem trzeba rozwiązać zupełnie inaczej.
| Sytuacja | Czy obfuskacja ma sens | Lepsze lub dodatkowe rozwiązanie |
|---|---|---|
| Komercyjny widżet, kalkulator lub skrypt sprzedawany klientom | Tak, utrudnia kopiowanie i usuwanie zabezpieczeń licencyjnych | Weryfikacja licencji po stronie serwera |
| Autorska logika w przeglądarce, np. algorytm konfiguratora | Tak, podnosi koszt analizy dla konkurencji | Przeniesienie najcenniejszej części na serwer |
| Ukrycie klucza API lub hasła w kodzie | Nie, klucz i tak da się wyciągnąć | Klucz wyłącznie po stronie serwera, przeglądarka łączy się przez własne API |
| Zabezpieczenie walidacji formularza lub ceny | Nie, żądanie można wysłać z pominięciem kodu strony | Walidacja i liczenie ceny na serwerze |
| Zwykła strona firmowa lub sklep | Zwykle nie, wystarczy minifikacja | Minifikacja i dbałość o wydajność |
Minifikacja a obfuskacja
Każdy nowoczesny proces budowania, np. w Vite czy Webpacku, minifikuje kod: usuwa spacje i komentarze oraz skraca nazwy lokalnych zmiennych. To już utrudnia czytanie, ale struktura kodu pozostaje taka sama i po sformatowaniu jest dość łatwa do zrozumienia.
Obfuskacja idzie dalej. Narzędzia takie jak javascript-obfuscator potrafią:
- przenosić napisy do zakodowanej tablicy, dzięki czemu w kodzie nie widać adresów, komunikatów ani nazw właściwości,
- spłaszczać przepływ sterowania, czyli zamieniać czytelne warunki i pętle na trudną do śledzenia maszynę stanów,
- dodawać martwy kod, który nigdy się nie wykona, ale utrudnia analizę,
- zmieniać nazwy także zmiennych globalnych i właściwości obiektów,
- utrudniać pracę z narzędziami deweloperskimi i wykrywać próby przeformatowania kodu.
Jak włączam obfuskację w procesie budowania
Nigdy nie zaciemniam całej aplikacji. Wydzielam do osobnego pliku tylko tę część, która faktycznie wymaga ochrony, a resztę zostawiam zwykłej minifikacji. Obfuskację uruchamiam jako ostatni krok budowania wersji produkcyjnej, przez wtyczkę do Vite lub Webpacka albo osobnym poleceniem na gotowym pliku. Przykładowa konfiguracja o umiarkowanej sile:
// obfuscator.config.js
module.exports = {
compact: true,
stringArray: true,
stringArrayEncoding: ['base64'],
stringArrayThreshold: 0.75,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 0.3,
deadCodeInjection: false,
renameGlobals: false,
sourceMap: false
};
Kilka zasad, których się trzymam:
- Umiarkowane progi. Spłaszczanie przepływu i martwy kod włączam tylko dla niewielkiej części kodu albo wcale, bo to one najbardziej spowalniają wykonanie i zwiększają rozmiar pliku.
- Bez publicznych map źródeł. Mapa źródeł opublikowana obok zaciemnionego pliku odtwarza oryginalny kod i niweczy całą pracę. Jeśli potrzebuję jej do analizy błędów, przechowuję ją prywatnie w narzędziu do monitorowania.
- Testy na wersji zaciemnionej. Agresywne ustawienia, zwłaszcza zmiana nazw globalnych i właściwości, potrafią zepsuć kod korzystający z zewnętrznych bibliotek. Automatyczne testy uruchamiam na pliku po obfuskacji, a nie przed nią.
Ile kosztuje to w wydajności
Obfuskacja zawsze ma swoją cenę. Zakodowane napisy trzeba odkodować w czasie działania, spłaszczony przepływ wykonuje się wolniej, a martwy kod zwiększa rozmiar pliku nawet kilkukrotnie. Na słabszych telefonach przekłada się to na dłuższe wykonywanie skryptów i wolniejszą reakcję na kliknięcia.
Dlatego przed wdrożeniem porównuję rozmiar pliku i czas wykonania przed obfuskacją i po niej, a limit rozmiaru zaciemnionego pliku wpisuję do budżetu wydajności. Kontrolę tę mam w procesie automatycznego sprawdzania szybkości i jakości kodu, więc zbyt agresywne ustawienia nie trafią na produkcję niezauważone. Jeśli zaciemniony kod obsługuje interakcje użytkownika, sprawdzam też jego wpływ na Interaction to Next Paint.
Czego obfuskacja nie zabezpieczy
Zaciemniony kod nadal wykonuje się w przeglądarce użytkownika, więc zdeterminowana osoba zawsze może go przeanalizować. Istnieją narzędzia do automatycznego odwracania popularnych technik obfuskacji, a modele AI dodatkowo przyspieszają odczytywanie zaciemnionego kodu. Obfuskacja podnosi koszt analizy, ale nie czyni jej niemożliwą.
Dlatego traktuję ją wyłącznie jako dodatkową warstwę. Wszystko, co decyduje o bezpieczeństwie, czyli uprawnienia, ceny, limity, klucze API i walidacja danych, musi działać po stronie serwera. Kod w przeglądarce może się mylić albo zostać podmieniony, a serwer i tak musi sprawdzić każde żądanie, tak samo jak przy ochronie bazy danych przed SQL Injection.
W przypadku PHP, np. przy komercyjnych wtyczkach do WordPressa, stosuje się osobne narzędzia do kodowania kodu źródłowego, które wymagają rozszerzenia na serwerze. To jednak zupełnie inny scenariusz niż kod JavaScript wysyłany do każdej przeglądarki.
Obfuskacja a SEO i analiza błędów
Zaciemniony skrypt nie szkodzi SEO, dopóki działa tak samo dla wszystkich i nie spowalnia renderowania. Problem pojawia się, gdy przez obfuskację treść strony ładuje się wolniej albo gdy zaciemniony kod pokazuje robotom inną treść niż użytkownikom. To drugie oznacza już ryzyko kary, bo granica między dynamic renderingiem a cloakingiem jest cienka. Treści i linków nigdy nie zaciemniam, zaciemniam wyłącznie logikę.
Druga praktyczna kwestia to błędy na produkcji. Stos wywołań z zaciemnionego kodu jest bezużyteczny bez prywatnej mapy źródeł, dlatego przy wdrożeniu obfuskacji od razu ustawiam ich przechowywanie w narzędziu do monitorowania błędów. Bez tego debugowanie kodu JavaScript po wdrożeniu obfuskacji zamienia się w zgadywanie.