Logo David Burdelak
Cybersecurity

Jak zaciemnić kod JavaScript i kiedy to ma sens

Obfuskacja kodu źródłowego

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.

SytuacjaCzy obfuskacja ma sensLepsze lub dodatkowe rozwiązanie
Komercyjny widżet, kalkulator lub skrypt sprzedawany klientomTak, utrudnia kopiowanie i usuwanie zabezpieczeń licencyjnychWeryfikacja licencji po stronie serwera
Autorska logika w przeglądarce, np. algorytm konfiguratoraTak, podnosi koszt analizy dla konkurencjiPrzeniesienie najcenniejszej części na serwer
Ukrycie klucza API lub hasła w kodzieNie, 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 cenyNie, żądanie można wysłać z pominięciem kodu stronyWalidacja i liczenie ceny na serwerze
Zwykła strona firmowa lub sklepZwykle nie, wystarczy minifikacjaMinifikacja 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.