Server Actions, Route Handlers, Server Components i proxy poszerzają powierzchnię ataku. Przejdźmy po kolei przez miejsca, które najczęściej pękają w prawdziwych aplikacjach.
1. XSS w Next.js, czyli Cross-Site Scripting
Gdzie pęka ochrona przed XSS?
to atak, w którym napastnik wstrzykuje złośliwy skrypt
wykonywany w przeglądarce ofiary. React domyślnie escapuje zawartość JSX, więc {userInput} jest bezpieczne. Ale są dwa klasyczne sposoby na obejście tej
ochrony.
dangerouslySetInnerHTML — najczęstszy wektor
URL-e z danych użytkownika
React 19 zastępuje URL-e javascript: w atrybutach funkcją zgłaszającą błąd, więc poniższy przykład nie wykona już payloadu w aktualnym stosie Next.js. Nie traktuj tego jednak jako ogólnego walidatora URL: starszy React, surowe API DOM, biblioteki zewnętrzne, przekierowania i inne miejsca użycia adresu mogą nie mieć tej ochrony.
Jak zamknąć lukę XSS w Next.js?
Open redirect — ten sam problem, inny wektor
Bliski krewny podatnego linku to open redirect: przekierowanie na adres pochodzący wprost od użytkownika. Najczęściej pojawia się przy powrotach po logowaniu (?next=...), gdzie atakujący podstawia obcą domenę i wysyła ofierze wiarygodnie wyglądający link do Twojej strony, który ląduje na phishingu.
Parsowanie przez URL jest pewniejsze niż samo startsWith('/'): normalizuje adres i pozwala odrzucić zarówno //evil.example, jak i mniej oczywiste warianty z ukośnikami wstecznymi. W produkcji trzymaj kanoniczny origin w zwalidowanej konfiguracji, nie wpisuj go w wielu plikach.
2. Wyciek danych z serwera do klienta w Next.js
Problem bezpieczeństwa
Server Components mogą pobierać wrażliwe dane (hasła, tokeny, pełne rekordy DB), ale jeśli przekażesz je do Client Componenta, React musi je zserializować w payloadzie RSC wysyłanym do przeglądarki. Nie oznacza to, że rekord staje się częścią statycznego pliku JavaScript, ale użytkownik nadal może odczytać przesłane dane w odpowiedzi sieciowej.
Rozwiązanie w Next.js
Przekazuj do Client Components tylko pola, które klient potrzebuje:
Dodatkowe zabezpieczenie — :
React 19 wprowadza experimental_taintObjectReference i experimental_taintUniqueValue — mechanizmy, które wyrzucą błąd, jeśli spróbujesz przekazać wrażliwy obiekt do Client Component. Nazwa nie jest przypadkowa, ponieważ to wciąż API eksperymentalne i domyślnie wyłączone. Żeby zadziałało, włącz je w konfiguracji:
Traktuj je więc jako dodatkową siatkę bezpieczeństwa, a nie jako podstawową linię obrony, ponieważ tą jest select i DTO powyżej.
3. Brak walidacji inputu w Server Actions
Problem walidacji danych
Server Actions tworzą publicznie osiągalne endpointy POST. Next.js utrudnia wywołanie ich z obcego originu, ale zalogowany użytkownik nadal może sam skonstruować żądanie i przesłać pola, których formularz nie pokazuje. Bez walidacji prowadzi to do mass assignment: nie wstrzykujesz kodu SQL, tylko przemycasz do zapytania pole, które nigdy nie powinno pochodzić od użytkownika.
Klasyczny SQL injection jest mniej prawdopodobny, gdy konsekwentnie używasz parametryzowanych metod ORM. Nie znaczy to jednak, że ORM automatycznie zabezpiecza każde API: surowe zapytania (db.$queryRawUnsafe, sklejane stringi SQL), dynamiczne fragmenty ORDER BY i filtry budowane bez allowlisty przywracają injection natychmiast. Parametryzuj wartości, a nazwy kolumn i dozwolone operatory mapuj z zamkniętej listy. Ta sama zasada dotyczy wywołań powłoki, nie sklejaj inputu użytkownika w komendę dla exec tylko użyj API bez shella i przekaż argumenty osobno.
Rozwiązanie przez walidację runtime
Server Action to publiczny endpoint POST. Nigdy nie przekazuj surowego formData do bazy i zawsze waliduj oraz ograniczaj pola.
CSRF w Route Handlerach, o którym Next.js Cię nie ostrzeże
Tu pojawia się luka, którą łatwo przeoczyć, bo dotyczy tylko części Twojego kodu. Server Actions mają wbudowaną ochronę: Next.js sprawdza, czy Origin żądania zgadza się z Host (lub X-Forwarded-Host), i wykonuje akcję tylko przez POST. To realne zabezpieczenie — ale działa wyłącznie dla Server Actions.
Własne Route Handlery (app/api/.../route.ts) mutujące dane na podstawie cookie sesyjnego tej ochrony nie dostają. Jeśli logujesz użytkownika przez cookie, taki endpoint jest podatny na klasyczny atak: obca strona wysyła do niego żądanie w tle, przeglądarka dołącza cookie automatycznie, a serwer wykonuje mutację, myśląc, że to autoryzowany użytkownik.
Najprostsza obrona to powtórzenie tego, co Server Actions robią automatycznie — odrzucenie żądań, których Origin nie zgadza się z hostem:
Sprawdzanie Origin jest dobrą warstwą obrony, ale musi uwzględniać topologię proxy i sposób obsługi żądań bez tego nagłówka. W wypadku operacji wysokiego ryzyka użyj sprawdzonego tokenu CSRF (synchronizer token albo poprawnie podpisany double-submit cookie), ustaw sesyjne cookies jako Secure, HttpOnly i co najmniej SameSite=Lax, a pomocniczo możesz filtrować Sec-Fetch-Site. Samo SameSite jest obroną warstwową, a nie uniwersalnym zamiennikiem tokenu. Przeniesienie mutacji do Server Actions daje wbudowaną kontrolę originu, lecz nie zwalnia z autoryzacji i walidacji. Więcej o samym uwierzytelnianiu, sesjach i tokenach znajdziesz w artykule o backendzie dla frontendowca: auth, real-time i integracje.
4. Brak nagłówków bezpieczeństwa w Next.js
Problem z security headers
Next.js nie zna modelu zagrożeń Twojej aplikacji, dlatego nie ustawi za Ciebie kompletnej polityki nagłówków. Bez niej przeglądarka nie dostaje m.in. jasnych reguł osadzania strony, ładowania skryptów i ujawniania referrera.
Rozwiązanie przez nagłówki bezpieczeństwa
HSTS wysyłaj wyłącznie po HTTPS. includeSubDomains dodaj dopiero wtedy, gdy każda subdomena działa po HTTPS; opcja preload wymaga jeszcze ostrożniejszej, świadomej decyzji. Clickjacking najlepiej blokować dyrektywą CSP frame-ancestors 'none'; X-Frame-Options: DENY możesz zachować jako warstwę zgodności dla starszych klientów.
Content Security Policy, czyli CSP w Next.js
to najpotężniejszy nagłówek bezpieczeństwa, który definiuje, z jakich źródeł przeglądarka może ładować skrypty, style, obrazy i inne zasoby. Tyle że to też nagłówek, który najłatwiej wdrożyć źle, więc przejdź go w całości, a nie połowicznie.
Choć to potężny nagłówek bezpieczeństwa kontrolujący źródła skryptów, stylów i innych zasobów, jest on również wyjątkowo podatny na błędy konfiguracyjne. Połowiczne wdrożenie CSP daje złudne poczucie bezpieczeństwa lub, co gorsza, potrafi całkowicie zablokować prawidłowe działanie aplikacji.
Jak to działa? Proxy generuje losowy nonce na każde żądanie, dopisuje go do dyrektywy script-src i przekazuje dalej, a Next.js oznacza tym nonce swoje własne skrypty. Kluczowe jest to drugie: bez przekazania nonce do aplikacji dyrektywa 'strict-dynamic' zablokuje skrypty samego Next.js i wyświetli białą stronę. Dlatego nonce wędruje w nagłówku żądania (x-nonce), skąd odczyta go layout:
5. Ekspozycja zmiennych środowiskowych w Next.js
Problem z publicznymi env vars
W Next.js odwołania do zmiennych z prefiksem NEXT_PUBLIC_ są zastępowane ich wartościami podczas next build i mogą trafić do kodu klienta. Zmienne bez tego prefiksu pozostają serwerowe, ale tylko dopóki sam nie przekażesz ich do Client Componenta, odpowiedzi API albo logów.
Rozwiązanie dla sekretów i zmiennych środowiskowych
Moduł z server-only nie może jednocześnie eksportować konfiguracji przeznaczonej dla Client Components. W praktyce rozdziel env.server.ts (sekrety i import 'server-only') od env.client.ts (wyłącznie jawna allowlista NEXT_PUBLIC_*). W tym drugim odwołuj się do zmiennych statycznie, np. process.env.NEXT_PUBLIC_APP_URL, bo dynamiczny dostęp przez process.env[name] nie zostanie wstawiony do bundle'a. Walidację uruchamiaj w procesie build/start; pamiętaj też, że publiczne wartości są zamrażane podczas buildu, więc ten sam artefakt promowany między środowiskami zachowa wartości z chwili kompilacji.
Sekret bez prefiksu też może wyciec
Brak NEXT_PUBLIC_ nie jest mechanizmem kontroli dostępu. Taki kod nadal ujawnia sekret:
Oprócz walidacji pilnuj granic serwer–klient, maskuj sekrety w logach i po wykryciu wycieku natychmiast rotuj dane dostępowe. Usunięcie wartości z repozytorium lub bundle'a nie unieważnia już ujawnionego klucza.
Bonus: rate limiting endpointów auth
Pięć luk powyżej zamyka najczęstsze dziury, ale jedna rzecz przewija się przez wszystkie i zasługuje na osobne zdanie: brak limitowania żądań. Walidacja i autoryzacja chronią przed pojedynczym złym żądaniem, ale nie przed tysiącem poprawnych. Endpoint logowania bez limitu to zaproszenie do brute force, a Server Action wysyłający e-maile bez limitu może zamienić Twoją domenę w narzędzie do spamu.
Minimalna ochrona to licznik na parę „akcja + IP (lub userId)", oparty o współdzielony store (Upstash Redis na edge, bo pamięć procesu nie przetrwa między wywołaniami serverless). Nakładaj go przede wszystkim na logowanie, rejestrację, reset hasła i każdą operację wysyłającą wiadomości. To część minimum bezpieczeństwa każdej publicznej aplikacji.
Bonus: SSRF przy pobieraniu URL-i na serwerze
Gdy Route Handler pobiera adres podany przez użytkownika, przykładowo do generowania podglądu linku, importu obrazu albo webhooka testowego, atakujący może skierować serwer na localhost, prywatny adres sieciowy lub endpoint metadanych chmury. To : żądanie wychodzi z zaufanej infrastruktury, więc omija bariery niedostępne z internetu.
Nie wystarczy sprawdzić, czy tekst zaczyna się od https://, więc używaj allowlisty hostów, po rozwiązaniu DNS odrzucaj adresy prywatne, link-local i loopback, wyłącz lub kontroluj przekierowania oraz ustaw limit czasu i rozmiaru odpowiedzi. Walidację powtarzaj po każdym redirectcie, bo bez tego bezpieczny URL może przekierować na zasób wewnętrzny. Jeśli funkcja biznesowa nie wymaga dowolnych hostów, nie dawaj jej takiej możliwości.
Lista kontrolna bezpieczeństwa Next.js: CSRF, XSS, injection i env vars
Przed wdrożeniem na produkcję sprawdź:
- Każdy
dangerouslySetInnerHTMLma sanityzację (DOMPurify), - URL-e od użytkownika (linki, redirecty
?next=) są walidowane — bezjavascript:i obcych domen, - Route Handlery mutujące na podstawie cookie sprawdzają
Origin/Host(CSRF) - Cookies sesyjne mają
Secure,HttpOnlyi odpowiednieSameSite; operacje wysokiego ryzyka używają tokenu CSRF, - Każdy Server Action waliduje input (Zod) i sprawdza autoryzację,
- Żadne pole trafiające do bazy nie pochodzi wprost z
formData(mass assignment), - Surowe SQL, nazwy kolumn i komendy systemowe nie są sklejane z inputem użytkownika,
- Client Components nie dostają wrażliwych danych (passwordHash, tokeny)
- Nagłówki bezpieczeństwa są skonfigurowane (CSP z
frame-ancestors, HSTS,nosniff), - Zmienne środowiskowe prywatne nie mają prefiksu
NEXT_PUBLIC_, - Rate limiting chroni endpointy auth i API,
- Serwer nie pobiera dowolnych URL-i; ochrona SSRF obejmuje DNS i przekierowania,
- Linki otwierane przez
target="_blank"używająnoopenertam, gdzie wspierasz starsze przeglądarki;noreferrerdodajesz tylko wtedy, gdy chcesz ukryć referrer.
