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

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. 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ść. 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. Dowolny tekst należy wyświetlać przez textContent lub value pola, zamiast przez innerHTML.

Haszowanie właściwych bajtów
TextEncoder koduje tekst JavaScript jako bajty UTF-8. TextDecoder wykonuje operację odwrotną dla wybranego kodowania. Operacje nadal wymagają pamięci i czasu. 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.
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. 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. 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.
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 może przenieść kosztowne obliczenia poza wątek interfejsu. 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. 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: secure context, Promise, no streaming
- TextEncoder: UTF-8 encoding
- TextDecoder: byte decoding
- Workers: communication and CSP
- CSP connect-src scope
- Secure contexts and localhost
- Content Security Policy Level 3
- OWASP: safe DOM output
- JavaScript memory management
- OWASP: password hashing