- Co włączyć i w jakiej kolejności
- Najważniejsze nagłówki i ich rola
- Praktyczna kolejność wdrożenia
- Wartości bezpieczne na start
- Konfiguracja na popularnych serwerach
- Nginx
- Apache HTTPD / .htaccess
- IIS (Windows)
- Node.js/Express i inne frameworki
- Budowa bezpiecznej polityki CSP krok po kroku
- Inwentaryzacja zasobów i minimalny szkielet
- Tryb Report-Only i zbieranie raportów
- Nonce i hash – wybór strategii
- Typowe integracje i wyjątki
- Testowanie, wdrażanie etapowe i monitorowanie
- Narzędzia testowe
- Wdrażanie etapowe
- Raportowanie i observability
- CI/CD i rollback
- Scenariusze zaawansowane i częste pułapki
- CDN, reverse proxy i Kubernetes
- CORS vs CORP/COEP/COOP
- Cache, nagłówki a zasoby statyczne
- Migracje i zgodność przeglądarek
- Checklist przed produkcją
- Gotowe wzorce wartości i przykładowe zestawy
- Zestaw konserwatywny (start)
- Zestaw dla aplikacji SPA z CDN
- Zestaw dla serwisów z osadzaniem ramek
- Zmiany bez przestojów
Bezpieczne serwowanie aplikacji webowej zaczyna się od właściwych nagłówków HTTP. To szybka, tania i skuteczna bariera przed clickjackingiem, wyciekiem refererów, wykonaniem niechcianego skryptu czy mieszaniem treści HTTP/HTTPS. W tej instrukcji krok po kroku włączysz kluczowe nagłówki, przygotujesz bezpieczną politykę zasobów, wdrożysz ją na popularnych serwerach (Nginx, Apache, IIS, Node) oraz sprawdzisz wynik narzędziami testującymi. Zadbamy też o stopniowe egzekwowanie i monitorowanie błędów.
Co włączyć i w jakiej kolejności
Najważniejsze nagłówki i ich rola
Pełną listę warto budować warstwowo. Oto fundamenty, które powinieneś wdrożyć w każdym projekcie:
- Content-Security-Policy (CSP) – kontroluje, skąd można ładować skrypty, style, obrazy, fonty czy ramki. Pierwszy etap to tryb obserwacyjny (Report-Only), a docelowo egzekwowanie. W tym tekście użyjemy skrótu Content-Security-Policy w roli kluczowego pojęcia.
- Strict-Transport-Security – wymusza HTTPS po pierwszej wizycie. Skrótowo będziemy mówić HSTS. Można go zgłosić do preloadera przeglądarek.
- X-Frame-Options – blokuje osadzanie strony w ramkach (zapobieganie clickjackingowi). Wyróżniamy X-Frame-Options z wartościami DENY/SAMEORIGIN.
- X-Content-Type-Options – zapobiega sniffingowi typów MIME. Flaga X-Content-Type-Options powinna być ustawiona na nosniff.
- Referrer-Policy – ogranicza przekazywanie adresu odsyłającego. Zalecane np. strict-origin-when-cross-origin. W dalszej części oznaczamy jako Referrer-Policy.
- Permissions-Policy – ogranicza dostęp do API przeglądarki (kamera, mikrofon, geolokalizacja itd.). W tekście stosujemy pojęcie Permissions-Policy.
- Cross-Origin-Opener-Policy – izolacja kontekstu okna względem zewnętrznych źródeł, przydatne z COEP/COEP. Wyróżniamy COOP.
- Cross-Origin-Embedder-Policy – kontrola osadzanych zasobów spoza originu, ważna dla SharedArrayBuffer i izolacji. Oznaczamy COEP.
- Cross-Origin-Resource-Policy – ogranicza, kto może używać twoich zasobów. Skrótowo CORP.
- Cross-Origin Resource Sharing – mechanizm udostępniania zasobów między originami; nie jest to nagłówek “bezpieczeństwa” per se, ale bywa współkonfigurowany. Będziemy mówić o CORS przy integracjach.
Dodatkowo rozważ: X-Permitted-Cross-Domain-Policies (Flash/Adobe – dziś historyczne, ale wciąż spotykane), Clear-Site-Data (czyszczenie storage), Report-To/Reporting-Endpoints (zbieranie raportów), atrybuty COOP/COEP preload. Nagłówki X-XSS-Protection i Expect-CT są przestarzałe – zazwyczaj nie włączaj ich, chyba że wymaga tego starsze środowisko.
Praktyczna kolejność wdrożenia
- Etap 1 (niski ryzyk): HSTS (bez preloadu), X-Frame-Options, X-Content-Type-Options, Referrer-Policy.
- Etap 2 (średni ryzyk): Permissions-Policy, CORP, COOP, COEP – początkowo w konfiguracjach najmniej restrykcyjnych.
- Etap 3 (wysoki wpływ): Content-Security-Policy w trybie Report-Only, zbieranie raportów, następnie egzekwowanie i uszczelnianie.
- Opcjonalnie: rejestracja HSTS Preload, Reporting-Endpoints, Clear-Site-Data przy migracjach.
Wartości bezpieczne na start
- X-Frame-Options: DENY (lub SAMEORIGIN, jeśli świadomie osadzasz wewnątrz iframów).
- X-Content-Type-Options: nosniff.
- Referrer-Policy: strict-origin-when-cross-origin (dobry kompromis między użytecznością a prywatnością).
- Permissions-Policy: stopniowo, np. geolocation=(), microphone=(), camera=().
- CORP: same-origin (dla wrażliwych zasobów), lub same-site gdy potrzebujesz współdzielić w obrębie tej samej strony.
- COOP: same-origin.
- COEP: require-corp.
- HSTS: max-age=31536000; includeSubDomains (po audycie), preload (po dłuższym okresie stabilności).
- CSP (start): default-src 'self’; object-src 'none’; base-uri 'self’; frame-ancestors 'none’.
Konfiguracja na popularnych serwerach
Nginx
1) Otwórz główną konfigurację serwera lub blok server: /etc/nginx/nginx.conf albo /etc/nginx/sites-available/example.conf.
2) W bloku server dla HTTPS dodaj przykładowe nagłówki:
- add_header Strict-Transport-Security „max-age=31536000” always;
- add_header X-Frame-Options „DENY” always;
- add_header X-Content-Type-Options „nosniff” always;
- add_header Referrer-Policy „strict-origin-when-cross-origin” always;
- add_header Permissions-Policy „geolocation=(), microphone=(), camera=()” always;
- add_header Cross-Origin-Opener-Policy „same-origin” always;
- add_header Cross-Origin-Embedder-Policy „require-corp” always;
- add_header Cross-Origin-Resource-Policy „same-origin” always;
- add_header Content-Security-Policy „default-src 'self’; object-src 'none’; base-uri 'self’; frame-ancestors 'none'” always;
3) Zrestartuj serwer: systemctl reload nginx. Uwaga: always gwarantuje nagłówek także dla odpowiedzi 4xx/5xx i przy przekierowaniach.
4) CSP z nonce/hash. Dla dynamicznych skryptów wygodną ścieżką jest nonce. W Nginx możesz wygenerować nonce zmienną map i przekazać do upstreamu albo użyć modułów trzecich. Popularna praktyka: aplikacja generuje nonce i zwraca go w odpowiedzi, a Nginx wstawia go do CSP za pomocą add_header z wartościami wstawianymi przez sub_filter lub bardziej zaawansowaną logiką w aplikacji (zalecane).
5) HSTS Preload: dopiero po kilku tygodniach stabilności dodaj includeSubDomains; preload i zgłoś domenę do hstspreload.org.
Apache HTTPD / .htaccess
1) Upewnij się, że działa moduł headers: a2enmod headers, a następnie systemctl reload apache2.
2) W pliku VirtualHost dla :443 lub w .htaccess dodaj:
- Header always set Strict-Transport-Security „max-age=31536000”
- Header always set X-Frame-Options „DENY”
- Header always set X-Content-Type-Options „nosniff”
- Header always set Referrer-Policy „strict-origin-when-cross-origin”
- Header always set Permissions-Policy „geolocation=(), microphone=(), camera=()”
- Header always set Cross-Origin-Opener-Policy „same-origin”
- Header always set Cross-Origin-Embedder-Policy „require-corp”
- Header always set Cross-Origin-Resource-Policy „same-origin”
- Header always set Content-Security-Policy „default-src 'self’; object-src 'none’; base-uri 'self’; frame-ancestors 'none'”
3) Jeżeli używasz .htaccess, pamiętaj o AllowOverride All w konfiguracji katalogu. Lepiej jednak ustawiać nagłówki w VirtualHost niż w .htaccess ze względów wydajnościowych.
IIS (Windows)
1) Otwórz web.config w katalogu aplikacji.
2) Wstaw do sekcji system.webServer/httpProtocol/customHeaders elementy add, np.:
- <add name=”Strict-Transport-Security” value=”max-age=31536000″ />
- <add name=”X-Frame-Options” value=”DENY” />
- <add name=”X-Content-Type-Options” value=”nosniff” />
- <add name=”Referrer-Policy” value=”strict-origin-when-cross-origin” />
- <add name=”Permissions-Policy” value=”geolocation=(), microphone=(), camera=()” />
- <add name=”Cross-Origin-Opener-Policy” value=”same-origin” />
- <add name=”Cross-Origin-Embedder-Policy” value=”require-corp” />
- <add name=”Cross-Origin-Resource-Policy” value=”same-origin” />
- <add name=”Content-Security-Policy” value=”default-src 'self’; object-src 'none’; base-uri 'self’; frame-ancestors 'none'” />
3) Zrestartuj aplikację w IIS Manager lub poleceniem iisreset. CSP z nonce realizuj w warstwie aplikacji (np. ASP.NET), generując nonce per żądanie i wstawiając do skryptów.
Node.js/Express i inne frameworki
1) Express: zainstaluj helmet (zależnie od wersji frameworka i Node). W kodzie aplikacji użyj helmet i skonfiguruj sekcje dla CSP, HSTS, frameguard (X-Frame-Options), noSniff (X-Content-Type-Options), referrerPolicy itd.
2) PHP: jeśli korzystasz z Apache/Nginx, preferuj ustawienia na poziomie serwera. Dodatkowo możesz wysyłać nagłówki funkcją header(…) z warstwy PHP. W przypadku CSP nonce generuj w PHP (np. losowe 16–24 bajty, Base64), wstaw do nagłówka i znaczników script.
3) Django/Flask: użyj middleware (django-secure, django-csp) lub własnych middlewarów. Analogicznie generuj nonce i egzekwuj polityki.
4) Serwery SSR/SPA: jeśli korzystasz z Next.js, Nuxt, Vite SSR – skonfiguruj nagłówki na warstwie proxy (Nginx) lub za pomocą mechanizmów serwera platformy (np. next.config.js dla nagłówków statycznych zasobów, a dla HTML – middleware).
Budowa bezpiecznej polityki CSP krok po kroku
Inwentaryzacja zasobów i minimalny szkielet
Zacznij od spisu źródeł zasobów: domeny skryptów, stylów, obrazów, fontów, mediów, połączeń XHR/WebSocket, ramek. Minimalny szkielet polityki:
- default-src 'self’
- object-src 'none’
- base-uri 'self’
- frame-ancestors 'none’ (lub 'self’ jeśli osadzasz panel w innych częściach witryny)
- form-action 'self’ (jeśli wysyłasz formularze)
- img-src 'self’ data: (jeśli naprawdę potrzebujesz data: dla obrazów; inaczej ogranicz)
Skrypty i style:
- script-src 'self’ 'nonce-RANDOM’ lub z hashami 'sha256-…’; unikaj 'unsafe-inline’ i 'unsafe-eval’.
- style-src 'self’ 'nonce-RANDOM’ lub, jeśli to niemożliwe, minimalizuj 'unsafe-inline’ i przejdź na nonce/hash w kolejnych iteracjach.
Połączenia sieciowe:
- connect-src 'self’ api.twojadomena.tld w zależności od potrzeb (SSE/WS/XHR/fetch).
- font-src 'self’ cdn.twojadomena.tld (jeżeli hostujesz fonty na CDN).
- media-src, frame-src tylko w razie konieczności.
Tryb Report-Only i zbieranie raportów
Przed egzekwowaniem użyj Content-Security-Policy-Report-Only, aby zaobserwować naruszenia bez blokowania:
- Content-Security-Policy-Report-Only: default-src 'self’; …; report-to default-endpoint
- Reporting-Endpoints lub Report-To: skonfiguruj endpoint raportowania, np. https://report.twojadomena.tld/csp-report.
Po kilku dniach/tygodniach analizy logów poprawiaj politykę i sukcesywnie przełączaj na Content-Security-Policy (tryb egzekwowania). Uwaga: niektóre przeglądarki mogą różnie obsługiwać Report-To i Reporting-Endpoints – przetestuj kilka.
Nonce i hash – wybór strategii
- Nonce: generowany per-odpowiedź, wstawiany w nagłówku CSP (script-src 'nonce-…’) i w atrybucie nonce każdego dozwolonego znacznika script/style. Wymaga ingerencji w HTML generowany przez serwer/aplikację.
- Hash: obliczony z treści skryptu inline (sha256/384/512). Stabilny, jeżeli skrypt nie zmienia się dynamicznie. Dla wielu małych inline’ów może być trudniejszy w utrzymaniu.
- Unikaj 'unsafe-inline’ i 'unsafe-eval’. Jeżeli biblioteka wymaga eval, poszukaj alternatywy lub ogranicz użycie do wąskich kontekstów.
Typowe integracje i wyjątki
- CDN ze skryptami: dodaj do script-src konkretną domenę CDN; rozważ Subresource Integrity (SRI) i stopniową migrację do hostowania własnego.
- Mapy, płatności, wideo: często wymagają frame-src i connect-src – dokumentuj każdy wyjątek i utrzymuj minimalny zakres.
- Analytics: preferuj self-hosted lub minimalny, ściśle wskazany zestaw domen. Wyłącz wildcardy typu *.example.com, jeśli nie musisz ich mieć.
- Service Worker: pamiętaj o script-src dla pliku SW i o tym, że SW może buforować odpowiedzi – zgodność nagłówków po zmianach CSP jest kluczowa.
Testowanie, wdrażanie etapowe i monitorowanie
Narzędzia testowe
- curl -I https://twojadomena.tld – szybka kontrola obecności nagłówków.
- Developer Tools w przeglądarce (zakładka Network, Security, Console) – zobaczysz naruszenia CSP, COOP/COEP/CORP.
- securityheaders.com – ocena zestawu nagłówków i wskazówki.
- Mozilla Observatory – skan i rekomendacje.
- Qualys SSL Labs – weryfikacja HTTPS, pomocna przy HSTS.
Wdrażanie etapowe
- Faza 0: Uruchom nagłówki niskiego ryzyka (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, wstępny HSTS bez includeSubDomains i bez preload).
- Faza 1: Dodaj Permissions-Policy, CORP, COOP, COEP z ostrożnymi wartościami. Monitoruj konsolę przeglądarki i logi.
- Faza 2: CSP w Report-Only. Zbieraj raporty (Reporting-Endpoints, logi serwera), usuwaj niepotrzebne inline’y, dodawaj nonce/hash.
- Faza 3: Przełącz CSP na egzekwowanie. Utrzymuj raportowanie, aby szybko wykrywać regresje.
- Faza 4: Po 4–8 tygodniach stabilności rozważ HSTS includeSubDomains i preload.
Raportowanie i observability
- Reporting-Endpoints: skonfiguruj niezależny, wysoko dostępny endpoint do zbierania raportów CSP/COOP/COEP. Przechowuj próbki (np. 30 dni), buduj dashboard (np. w ELK, Grafana, Datadog).
- Alerting: reguły na gwałtowne wzrosty naruszeń per ścieżka, per przeglądarka, per release.
- Korelacja z wersją: dodawaj nagłówek X-Release lub parametry wersji, aby łączyć naruszenia z konkretnym wdrożeniem.
CI/CD i rollback
- Testy E2E i smoke: skrypty, które weryfikują kluczowe flow pod kątem nagłówków (np. sprawdzają obecność i wartości przez curl lub biblioteki HTTP).
- Feature flags: przełączniki, które pozwalają szybko zmiękczyć politykę (np. dodać tymczasowo dodatkowe domeny w CSP) w razie incydentu.
- Canary i procentowe rollouty: najpierw mały procent ruchu, potem 100% po weryfikacji raportów.
Scenariusze zaawansowane i częste pułapki
CDN, reverse proxy i Kubernetes
- Cloudflare: ustaw HTTP Response Headers w Rules lub Transform Rules; pamiętaj o bypass dla zasobów, które muszą mieć inne polityki (np. pliki do osadzenia).
- Fastly/Akamai: dodaj custom response headers w VCL/konfiguracji; testuj różnice między edge a origin.
- Kubernetes: w NGINX Ingress użyj annotations do dodawania nagłówków (nginx.ingress.kubernetes.io/configuration-snippet lub add-header w ConfigMap). Upewnij się, że nie dublujesz nagłówków w samym podzie.
- Load balancery (ALB/ELB): jeśli wstrzykujesz nagłówki na LB, nie powielaj ich w aplikacji – trzymaj jedno źródło prawdy.
CORS vs CORP/COEP/COOP
- CORS reguluje, czy przeglądarka może odczytać zasób z innego originu; CORP/COEP/COOP dotyczą izolacji i osadzania. Niezależne od siebie – konfiguruj świadomie.
- COEP: require-corp wymaga, aby osadzane zasoby zwracały CORP: same-origin lub cross-origin z odpowiednimi nagłówkami.
- COOP: same-origin izoluje twoje okno od cross-origin. Zwróć uwagę na integracje typu płatności i otwieranie nowych okien – przetestuj.
Cache, nagłówki a zasoby statyczne
- CDN może buforować odpowiedzi z nagłówkami. Jeśli zmienisz politykę, zasoby w cache mogą mieć stare wartości. Stosuj wersjonowanie ścieżek, kontroluj Cache-Control dla HTML vs. assetów.
- HTML powinien być krótko cache’owany lub no-store, a polityka CSP/HSTS w nim – aktualna. Assety mogą mieć długi cache, ale zwykle nie niosą CSP; uważaj na CORP/COEP przy osadzaniu.
- Redirekty: dodaj nagłówki również na 301/302 (w Nginx/Apache używaj always), inaczej pierwsze żądanie nie otrzyma np. HSTS.
Migracje i zgodność przeglądarek
- Starsze przeglądarki mogą ignorować część dyrektyw (np. zaawansowane CSP). Projektuj politykę tak, by degradacja była bezpieczna.
- X-XSS-Protection: przestarzały, bywa ignorowany; polegaj na CSP i bezpiecznym kodowaniu danych.
- HSTS preload: usunięcie z listy trwa długo; zanim dodasz preload, upewnij się, że cała domena i subdomeny są dostępne wyłącznie przez HTTPS.
- Mixed content: włącz upgrade-insecure-requests w CSP, ale finalnie usuń wszystkie linki HTTP – to tylko koło ratunkowe.
Checklist przed produkcją
- HTTPS wszędzie, certyfikaty ważne i automatycznie odnawiane.
- HSTS bez preloadu działa poprawnie przez kilka tygodni; brak raportów o problemach subdomen.
- CSP w Report-Only nie generuje krytycznych naruszeń; po przełączeniu na egzekwowanie aplikacja działa.
- Permissions-Policy ogranicza nieużywane API; brak regresji funkcji.
- COOP/COEP/CORP nie blokują potrzebnych osadzeń; integracje zewnętrzne (płatności, wideo, SSO) przetestowane.
- Automaty testowe sprawdzają nagłówki na kluczowych ścieżkach (publiczne, logowanie, panel, API).
Gotowe wzorce wartości i przykładowe zestawy
Zestaw konserwatywny (start)
- Strict-Transport-Security: max-age=31536000
- X-Frame-Options: DENY
- X-Content-Type-Options: nosniff
- Referrer-Policy: strict-origin-when-cross-origin
- Permissions-Policy: geolocation=(), microphone=(), camera=()
- Cross-Origin-Opener-Policy: same-origin
- Cross-Origin-Embedder-Policy: require-corp
- Cross-Origin-Resource-Policy: same-origin
- Content-Security-Policy: default-src 'self’; object-src 'none’; base-uri 'self’; frame-ancestors 'none’; img-src 'self’; script-src 'self’; style-src 'self’
Uwaga: jeżeli korzystasz z fontów/obrazów z CDN, dodaj odpowiednie domeny. W przypadku inline stylów/skryptów – przejdź na nonce/hash.
Zestaw dla aplikacji SPA z CDN
- Strict-Transport-Security: max-age=31536000; includeSubDomains
- X-Frame-Options: SAMEORIGIN (jeśli panel osadzasz wewnętrznie) lub DENY
- X-Content-Type-Options: nosniff
- Referrer-Policy: strict-origin-when-cross-origin
- Permissions-Policy: geolocation=(), microphone=(), camera=(), usb=()
- Cross-Origin-Opener-Policy: same-origin
- Cross-Origin-Embedder-Policy: require-corp
- Cross-Origin-Resource-Policy: same-origin (dla krytycznych), cross-origin (dla publicznych assetów, jeśli to konieczne)
- Content-Security-Policy: default-src 'self’; object-src 'none’; base-uri 'self’; frame-ancestors 'none’; connect-src 'self’ api.twojadomena.tld; img-src 'self’ data: cdn.twojadomena.tld; font-src 'self’ cdn.twojadomena.tld; script-src 'self’ cdn.twojadomena.tld 'nonce-RANDOM’; style-src 'self’ cdn.twojadomena.tld 'nonce-RANDOM’; upgrade-insecure-requests
Zestaw dla serwisów z osadzaniem ramek
- Zamiast X-Frame-Options: używaj CSP frame-ancestors, które precyzyjniej kontroluje osadzanie (np. frame-ancestors https://partner.example).
- Dodaj frame-src dozwolone źródła, jeśli wyświetlasz cudze treści w iframe.
- Pilnuj, by domeny partnerów były ściśle wylistowane – unikaj wildcardów.
Zmiany bez przestojów
- Wersjonuj politykę (komentarz, osobny plik konfiguracyjny) i używaj rolloutów procentowych na warstwie LB/CDN.
- Utrzymuj listę wyjątków z datą dodania i celem usunięcia. Przeglądaj co sprint.
- Dokumentuj powiązania między polityką a komponentami front-end (np. które moduły wymagają dodatkowych dyrektyw).