Bezpieczeństwo
Nasz model bezpieczeństwa w jednym zdaniu: na serwerze nie ma nic do zaatakowania. Wszystko poniżej można zweryfikować z poziomu DevTools.
Architektura
Generator to statyczna aplikacja jednostronicowa serwowana z Cloudflare Pages: bez kont użytkowników, bez uwierzytelniania i bez żadnej ścieżki kodu backendowego w przepływie generowania. Każda operacja generowania, kodowania, skanowania i renderowania kodu QR odbywa się w całości w przeglądarce. Obok działa jeden mały endpoint serverless, wymieniony tutaj, a nie ukryty: /api/contact (formularz kontaktowy, który zapisuje to, co przesyłasz, w Cloudflare D1 i wysyła nam e-mailem). Nie jest wywoływany podczas tworzenia kodu QR; sprawdzanie bezpieczeństwa URL to osobny produkt pod adresem check.qr.abundera.ai. Sprawdź, czy kod QR jest bezpieczny
Model zagrożeń
Ponieważ generator nie zbiera, nie przechowuje ani nie przesyła żadnych danych użytkownika (dwa powyższe endpointy obsługują wyłącznie to, co jawnie do nich wyślesz), najczęstsze zagrożenia aplikacji webowych, kradzież danych logowania, naruszenie bazy danych, przejęcie sesji, wstrzyknięcie po stronie serwera, nie mają do niego zastosowania. Pozostała powierzchnia ataku to statyczny pakiet zasobów (HTML, CSS, JavaScript) serwowany z naszego origin. Projektujemy zakładając:
- Kompromitacja origin: atakujący zastępuje jeden z naszych spakowanych zasobów złośliwym. CSP poniżej ogranicza promień rażenia; tokeny cache-buster ułatwiają przywrócenie do zweryfikowanej dobrej wersji.
- Dane wprowadzone przez użytkownika: ładunki QR, nazwy vCard, hasła WiFi, wiersze CSV, zawartość zeskanowanych obrazów. Każda ścieżka wejściowa jest traktowana jako niezaufana podczas renderowania do DOM.
- Obrazy dostarczane przez użytkownika: adresy URL zdjęć vCard i przesyłane logo. Obrazy są renderowane na kanwasie, nigdy nie są osadzane w DOM jako surowy znacznik.
- Zewnętrzne rozszerzenia przeglądarki: poza zakresem. Jeśli rozszerzenie w przeglądarce ma uprawnienia do modyfikowania każdej strony, może modyfikować też naszą. Nasze gwarancje obowiązują, gdy strona jest załadowana bez modyfikacji.
Content Security Policy, dyrektywa po dyrektywie
Poniższa polityka jest odczytywana z wdrożonego pliku _headers w momencie budowania tej strony, więc nie może rozjechać się z tym, co wysyła edge. Zweryfikuj ją w nagłówkach odpowiedzi dowolnego żądania.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://abundera.ai 'wasm-unsafe-eval' https://challenges.cloudflare.com;
worker-src 'self' blob:;
style-src 'self' 'unsafe-inline' https://abundera.ai;
font-src 'self';
img-src 'self' data: blob: https:;
connect-src 'self' https: https://challenges.cloudflare.com;
frame-src 'self' https://challenges.cloudflare.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'Co każda dyrektywa nam umożliwia i gdzie stanowi kompromis:
default-src 'self', twarda podstawa. Wszystko, czego jawnie nie rozluźniamy, pozostaje same-origin.script-src 'self' https://abundera.ai 'wasm-unsafe-eval' https://challenges.cloudflare.com, żadnych inline<script>, żadnegoeval().abundera.aito nasza własna domena nadrzędna i serwuje jeden współdzielony skrypt, przełącznik języka.challenges.cloudflare.comto widżet Turnstile używany wyłącznie na /contact/.'wasm-unsafe-eval'jest wymagane przez ścieżkę kompilacji WebAssembly naszej biblioteki kodera QR; NIE zezwala na tradycyjnyeval(). Nasz własny pre-deploy check skanuje każdą stronę HTML pod kątem skryptów inline i przerywa build.style-src 'self' 'unsafe-inline', realne ustępstwo. Nasz podgląd QR oblicza inline kolory na poziomie pikseli (atrybuty style na poszczególnych modułach). Allowlist oparta na hashu działałaby, ale zawodziłaby przy każdej aktualizacji stylu bez wdrożenia. Kompromis: akceptujemy nieco słabszą politykę stylów; stylowanie i tak nie może eksfiltrować danych (CSS nie ma zasięguconnect-src).img-src 'self' data: blob: https:,data:dla renderów QR inline,blob:dla URL-i pobierania eksportu,https:dla URL-i zdjęć vCard i awatarów podanych przez użytkownika. URL-e podane przez użytkownika nigdy nie są wykonywane, tylko renderowane. Miej na uwadze, że ta dyrektywa jest też kanałem wychodzącym: dowolny skrypt działający na stronie mógłby zażądać obrazu z dowolnego hosta HTTPS i umieścić dane w URL-u. Akceptujemy to, bo funkcja tego wymaga, i mówimy o tym otwarcie zamiast opisywać politykę jako szczelną.connect-src 'self' https: https://challenges.cloudflare.com, ta dyrektywa wymaga uważnej lektury.https:to źródło typu schemat: pozwalafetch(), XMLHttpRequest, WebSocket przez TLS, EventSource isendBeacon()dotrzeć do dowolnego origin HTTPS, nie tylko tych, które wpisałeś. Jest tam, żeby pole zdjęcia vCard i sprawdzenie bezpieczeństwa URL mogły pobrać adres, który wkleisz. Nie zapobiega eksfiltracji przez złośliwy skrypt; żadna dyrektywa CSP tego nie robi, gdy skrypt już działa jako my. Przed eksfiltracją chroni to, że nasz wysyłany kod nie wykonuje takich żądań, co możesz obserwować w zakładce Sieć.frame-src 'self' https://challenges.cloudflare.com, tylko widżet Turnstile może być osadzony w ramce.frame-ancestors 'none', żadna inna strona nie może nas osadzić. Zapobiega clickjackingowi.base-uri 'self', złośliwie wstrzyknięty tag<base>nie może przekierować względnych URL-i do origin atakującego.form-action 'self', wstrzyknięty formularz może wysyłać dane tylko do naszego origin. Formularz kontaktowy to jedyny formularz, który cokolwiek wysyła.
Czego CSP nie potrafi
CSP ogranicza, skąd przeglądarka może wczytać kod. Nie sprawdza, co ten wczytany kod robi. Gdyby nasz origin został skompromitowany, pierwszy punkt modelu zagrożeń, skrypt atakującego działałby z tymi samymi uprawnieniami co nasz, w tym z uprawnieniami connect-src i img-src wymienionymi powyżej. Nasze zabezpieczenia przed tym stoją o krok przed CSP: wdrożenia idą przez jedno konto Cloudflare z tokenami o ograniczonym zakresie, nazwy plików zasobów są hashowane na podstawie treści, więc podmieniony plik zmienia każdy URL, który się do niego odwołuje, a service worker cache'uje wyłącznie odpowiedzi GET z tego samego origin. Manifest opisuje stronę taką, jaka jest wdrożona. To nie jest twierdzenie, że żadna strona internetowa nigdy nie mogłaby nigdzie wysłać danych, i wolimy, żebyś rozliczał nas z węższego, sprawdzalnego stwierdzenia.
Różne polityki CSP mają zastosowanie do /bio/* (rozluźnione img-src dla awatarów podanych przez użytkownika) i /embed/* (rozluźnione frame-ancestors dla zamierzonego osadzania). Obie są udokumentowane w site/_headers.
Nagłówki transportu i ramkowania
- HSTS:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload, 1 rok, wszystkie subdomeny, kwalifikuje się do listy preload HSTS. Przeglądarki zgodne odmawiają downgrade do HTTP. - X-Frame-Options: DENY, nadmiarowy wobec CSP
frame-ancestors, zachowany dla starszych przeglądarek. - X-Content-Type-Options: nosniff, zapobiega atakom przez pomylenie MIME.
- Referrer-Policy: strict-origin-when-cross-origin, kliknięcia linków wychodzących ujawniają origin, ale nie ścieżkę.
- Permissions-Policy:
camera=(self), microphone=(), geolocation=(), kamera dozwolona tylko dla naszego własnego skanera; mikrofon i lokalizacja jawnie zablokowane nawet po osadzeniu.
Service worker
Nasz service worker (site/sw.js) buforuje tylko zasoby same-origin. Procedura obsługi fetch jawnie odrzuca żądania cross-origin i metody inne niż GET, logikę możesz przeczytać w pliku /sw.js (źródło jest na tyle nieminifikowane, by dało się je śledzić). Zapisy do pamięci podręcznej są opakowane w event.waitUntil(), aby nie mogły zostać porzucone w trakcie nawigacji.
Sanityzacja danych wejściowych
Każda ścieżka renderowania akceptująca dane od użytkownika traktuje je jako niezaufany tekst:
- Podglądy ładunku QR używają
textContent, nieinnerHTML. - Cele udostępniania (schowek,
navigator.share()) przekazują tekst użytkownika jako ciąg znaków, nigdy jako znacznik. - Eksporty SVG są generowane przez naszą bibliotekę kodera; treści użytkownika są zakodowane base64 w
<image>xlink:href, nie wstrzykiwane jako elementy SVG. - Podglądy wydruku używają blob URL, nie
document.write(). - Parsowanie localStorage jest opakowane w
try/catch, uszkodzony wpis daje świeże puste ustawienie domyślne, nigdy wyjątek, który mógłby rozwinąć się w aktywną ścieżkę kodu. - URL-e podane przez użytkownika wyświetlane jako przyciski "Otwórz link" są ograniczone do
http(s)://, schematyjavascript:idata:są odrzucane.
Pobieranie obrazów cross-origin
Gdy użytkownik wkleja URL https: jako zdjęcie vCard lub logo, przeglądarka pobiera go z zastrzeżeniem CORS i allowlisty img-src naszego CSP. Obraz jest renderowany na kanwasie. Nigdy nie staje się żywym DOM, nie jest wykonywany jako kod i nie dociera do naszego origin, fetch to przeglądarka → zdalny obraz, a wynik jest malowany po stronie klienta. Atakujący kontrolujący zdalny URL obrazu może śledzić załadowanie tego URL (wpis w logach własnego serwera), ale nie może eksfiltrować niczego z naszej strony.
Subresource Integrity (SRI)
Cały JavaScript i CSS, który dostarczamy, jest same-origin. Nie ładujemy zewnętrznych skryptów ani arkuszy stylów, więc hashe SRI nie mają zastosowania. Jeśli kiedykolwiek załadujemy zewnętrzny zasób, dodamy do niego atrybut integrity SRI i udokumentujemy proces aktualizacji hasha na tej stronie.
Zgłaszanie luki bezpieczeństwa
Jeśli odkryjesz problem z bezpieczeństwem dotyczący Abundera QR, czy to w naszym kodzie, wdrożeniu, czy w zależności, którą dostarczamy, zgłoś go prywatnie na adres security@abundera.ai. Dążymy do triage'u w ciągu 72 godzin. Możesz się z nami skontaktować również przez dane kontaktowe w naszym pliku /.well-known/security.txt.
Brak programu bug bounty (na razie)
Nie oferujemy obecnie płatnych nagród, ale każdy potwierdzony ważny raport otrzymuje uznanie w changelogu i nasze publiczne podziękowania.
Zweryfikuj powyższe
Każde twierdzenie na tej stronie można zweryfikować z poziomu DevTools przeglądarki bez konieczności ufania nam:
- CSP: DevTools → Sieć →
/→ Nagłówki. Odczytaj nagłówek odpowiedziContent-Security-Policy. - HSTS + nagłówki bezpieczeństwa: to samo miejsce, wszystkie
Strict-Transport-Security,X-Frame-Optionsitp. są widoczne. - Brak wychodzących wywołań: DevTools → Sieć → Fetch/XHR. Wygeneruj QR. Obserwuj, jak licznik pozostaje na zero.
- Zakres service workera: DevTools → Aplikacja → Service Workers. Sprawdź źródło skryptu i listę zbuforowanych zasobów.
- Brak ciasteczek: DevTools → Aplikacja → Pliki cookie. Puste.
- Pełny przewodnik: sekcja weryfikacji w manifeście.
Kontakt
Zgłoszenia bezpieczeństwa: security@abundera.ai