Zero-Server-Side Browser Tools: Jak projektować bezpieczne narzędzia w czystym JS

Management Summary
Narzędzie przeglądarkowe może wykonywać obliczenia lub przekształcać tekst na urządzeniu, na którym otwarta jest strona, bez wysyłania wpisanej treści na serwer aplikacji. Ogranicza to zbędne udostępnianie danych w prostych narzędziach. Artykuł wyjaśnia zasadę lokalnego przetwarzania i jej granice. Samo pobranie strony nadal wymaga połączenia z serwerem, a inny kod na stronie może mieć dostęp do tych samych danych. Lokalna praca nie stanowi więc pełnej gwarancji prywatności.
Spis treści
Lokalne narzędzie przeglądarkowe może haszować, analizować lub przekształcać dane bez wysyłania tych treści do serwera aplikacji. Korzyścią jest mniejszy przepływ danych: endpoint przetwarzający nie potrzebuje poufnej zawartości. Sama strona nadal może być pobierana z sieci, a serwer hostingu otrzymywać metadane żądania, np. adres IP, żądaną ścieżkę i czas. Określenie „przetwarzanie lokalne” wymaga więc precyzyjnego zakresu.
Granica zaufania przy lokalnym przetwarzaniu
Piaskownica przeglądarki oddziela treści internetowe od części systemu operacyjnego. Kod na tej samej stronie nadal może odczytywać pola wejściowe. Przejęty skrypt, niebezpieczne wstawianie HTML lub rozszerzenie z odpowiednimi uprawnieniami może naruszyć poufność. Framework to szkielet oprogramowania ze strukturą i funkcjami do ponownego użycia. Vanilla JavaScript oznacza kod bez frameworka, a nie gwarancję bezpieczeństwa.
Mała liczba zależności, dostarczanie przez HTTPS, przegląd kodu i brak analityki lub nagrywania sesji przy polach wejściowych ograniczają ryzyko. Dane wejściowe nie powinny trafiać do URL-i, logów aplikacji ani telemetrii błędów, która wysyła informacje diagnostyczne o awariach. Dowolny tekst należy wyświetlać przez textContent lub value pola, zamiast przez innerHTML.

Haszowanie właściwych bajtów
UTF-8 określa sposób zapisu znaków jako bajtów, czyli jednostek liczbowych przechowywanych lub przesyłanych przez komputer. TextEncoder koduje tekst JavaScript jako bajty UTF-8. TextDecoder wykonuje operację odwrotną dla wybranego kodowania. Operacje nadal wymagają pamięci i czasu. Promise reprezentuje wynik, który będzie dostępny później. crypto.subtle.digest() zwraca Promise, ale przygotowanie tekstu i pętle mogą nadal obciążać główny wątek. Akceleracja sprzętowa i zerowe opóźnienie nie są gwarantowane.
SHA-256 oblicza odcisk wejściowych bajtów o stałej długości. Przykład oblicza SHA-256 reprezentacji UTF-8 bez usuwania spacji i zmiany wielkości liter. Limit miliona jednostek kodowych UTF-16 jest przykładowym ograniczeniem aplikacji, a nie gwarancją przeglądarki ani limitem bajtów. Niektóre znaki, np. wiele emoji, zajmują dwie jednostki UTF-16. Limit nie liczy więc bezpośrednio widocznych znaków. Interfejs wywołujący musi czytelnie prezentować odrzucone dane i błędy.
async function sha256Text(text) {
if (typeof text !== 'string') throw new TypeError('Expected text');
if (text.length > 1_000_000) throw new RangeError('Input too large');
if (!globalThis.isSecureContext || !globalThis.crypto?.subtle) {
throw new Error('Web Crypto requires a supported secure context');
}
const bytes = new TextEncoder().encode(text);
const digest = await crypto.subtle.digest('SHA-256', bytes);
return Array.from(new Uint8Array(digest),
byte => byte.toString(16).padStart(2, '0')).join('');
}
Suma kontrolna pliku powinna obejmować oryginalne bajty, a nie tekst ponownie zdekodowany i zakodowany. digest() wymaga całego wejścia w pamięci i nie obsługuje przyrostowego strumieniowania. Bardzo duże pliki wymagają innej, sprawdzonej implementacji oraz zmierzonych limitów zasobów. Sam SHA-256 nie jest szyfrowaniem ani odpowiednią metodą przechowywania haseł. Skrót przewidywalnych danych osobowych nie staje się automatycznie anonimowy.
CSP ogranicza określone kanały transmisji
connect-src 'self' dopuszcza połączenia skryptowe z własnym originem, czyli połączeniem protokołu, nazwy hosta i portu. connect-src 'none' blokuje API objęte tą dyrektywą, między innymi fetch, WebSocket i beacony. Obrazy, formularze, ramki i ładowanie skryptów mają osobne reguły. Polityka lokalnego narzędzia musi uwzględniać również te drogi i ściśle ograniczać wykonywany kod.
Content Security Policy (CSP) określa dozwolone zasoby i działania strony. CSP jest egzekwowana przez przeglądarkę i stanowi dodatkową warstwę ochrony. Nie jest jednak ogólnym firewallem sieciowym ani matematycznym dowodem braku wycieku. Nawigacja i dozwolone pobieranie zasobów wymagają osobnej oceny. Skrypty hostowane lokalnie również mogą zostać przejęte. Polityka egzekwująca wymaga testów; Report-Only zgłasza naruszenia bez blokowania.
Workers poprawiają responsywność, nie poufność
Web Worker uruchamia JavaScript w oddzielnym środowisku wykonania. Może przenieść kosztowne obliczenia poza wątek obsługujący interakcje ze stroną. DOM to drzewo elementów strony. Worker nie ma bezpośredniego dostępu do DOM, ale strona przekazuje wejście i odbiera wynik. Złośliwy kod strony może przechwycić oba etapy. Workers mogą również wykonywać żądania sieciowe.
Osobny plik workera wymaga odpowiedniego nagłówka CSP dla własnego kontekstu wykonania. Dyrektywa worker-src strony kontroluje ładowanie workera, a nie automatycznie późniejsze połączenia. Worker z adresu blob dziedziczy politykę twórcy i wymaga odpowiedniego zezwolenia na uruchomienie. Osobny plik ułatwia przegląd, ale sama suma kontrolna nie potwierdza braku komunikacji.
Dowody i ograniczenia
Przydatny test obejmuje ładowanie, wprowadzanie danych, przetwarzanie, kopiowanie lub pobieranie wyniku, błędy i czyszczenie. Kontrola sieci powinna obejmować workers i ewentualny aktywny service worker. Service worker może przechwytywać żądania witryny i dostarczać zapisane odpowiedzi. Próba offline po załadowaniu potwierdza sprawdzoną wersję oraz przebieg, a nie każdą przyszłą aktualizację. Środowisko testowe z zablokowaną siecią daje dodatkowy dowód.
Wyczyszczenie pól i usunięcie referencji ogranicza przypadkowe ponowne użycie. JavaScript nie może jednak zagwarantować usunięcia wszystkich kopii z pamięci przeglądarki i systemu w sposób odporny na analizę informatyczną. Schowek, pobrane pliki i celowo zapisane dane mogą przetrwać zamknięcie karty. Unikanie zbędnego zapisu jest bardziej wiarygodne niż obietnica całkowitego usunięcia przy zamknięciu.
Pytania i odpowiedzi
Czy worker podlega tej samej CSP co strona?
Zwykły osobny plik ma własny kontekst i potrzebuje nagłówka CSP w odpowiedzi dostarczającej skrypt. Zezwolenie worker-src dotyczy utworzenia workera. Dla blob obowiązuje dziedziczenie polityki twórcy; brak odpowiedniego zezwolenia blokuje uruchomienie. Test powinien obejmować zarówno start, jak i próbę połączenia z wnętrza workera.
Gdzie działa Web Crypto?
crypto.subtle wymaga obsługiwanego bezpiecznego kontekstu. HTTPS i typowy localhost podczas developmentu są odpowiednie; zwykły adres HTTP w prywatnej sieci LAN zazwyczaj nie jest. Przykład sprawdza kontekst oraz dostępność API.
Źródła
- MDN Web Docs
- OWASP Top 10
- Web Crypto digest: bezpieczny kontekst, Promise, brak strumieniowania
- TextEncoder: kodowanie UTF-8
- TextDecoder: dekodowanie bajtów
- Workers: komunikacja i CSP
- CSP: zakres connect-src
- Bezpieczne konteksty i localhost
- Content Security Policy Level 3
- OWASP: bezpieczne wyświetlanie w DOM
- JavaScript: zarządzanie pamięcią
- OWASP: haszowanie haseł