Dokument bezpieczeństwa dla klientów
Bezpieczeństwo FluxCRM: osobna instancja, szyfrowany transport, pełny dziennik zmian
Środki techniczne i organizacyjne, które faktycznie działają w produkcie, oraz to, czego jeszcze nie ma.
Każda firma ma osobną instancję i bazę, a każdą zmianę w danych widać w dzienniku audytu.
Zasady tego dokumentu
Opisujemy tylko to, co działa w kodzie i we wdrożeniu. Brak zapisu oznacza, że danego środka nie deklarujemy. Dokument jest przeglądany raz na kwartał razem z resztą dokumentacji.
Izolacja i hosting
- Jedna firma, jedna instancja. Każdy klient ma osobny kontener, własną bazę SQLite, własny klucz API i własną domenę. Nie ma wspólnej bazy z innymi klientami.
- Serwer. Instancje działają na serwerze VPS u dostawcy OVH, w kontenerach Docker, jako użytkownik bez uprawnień administratora (uid 10001). Katalog danych instancji ma ograniczone uprawnienia, a pliki z sekretami mają tryb 600.
- Transport. Cały ruch idzie przez HTTPS (certyfikaty Let's Encrypt odnawiane automatycznie przez Caddy). Ciasteczko sesji ma flagi
HttpOnlyiSameSite=Strict, a za proxy HTTPS takżeSecure. - Szyfrowanie plików. Pliki wgrane przez klientów w portalu, pliki wysłane klientom, skany i zdjęcia leżą zaszyfrowane (uwierzytelniony szyfr strumieniowy, klucz w pliku poza bazą, z rotacją). Zmieniony bajt albo zły klucz daje błąd, nigdy cichy zły wynik.
- Brak zależności zewnętrznych w kodzie. Serwer korzysta wyłącznie z biblioteki standardowej Pythona. Nie ma pakietów, które mogłyby wnieść podatność z łańcucha dostaw (zestawienie: licencje bibliotek w dokumentacji technicznej).
Dostęp i uwierzytelnianie
- Konta i role. Właściciel, handlowiec, tylko odczyt. Rola "tylko odczyt" nie może niczego zmienić: każda zmiana kończy się odmową.
- Hasła. Minimum 10 znaków domyślnie; właściciel zaostrza politykę w panelu (długość, wielka litera, cyfra, znak specjalny, brak loginu w haśle). Hasła są przechowywane wyłącznie jako skrót scrypt (N=2^14, r=8, p=1) i nigdy nie są zapisywane jawnie ani logowane.
- 2FA. Kod jednorazowy TOTP (RFC 6238) włączany per konto; właściciel może wymusić go dla roli właściciela (bez niego panel nie pozwala pracować). Klucze dostępu (passkeys, WebAuthn, ES256) zastępują hasło razem z drugim składnikiem: wymagamy potwierdzenia użytkownika (biometria albo PIN), a licznik podpisów wykrywa sklonowany klucz.
- Logowanie linkiem z maila. Konto bez 2FA i z zapisanym adresem e-mail może poprosić o jednorazowy link (ważny 15 minut). Token jest we fragmencie adresu, więc skanery poczty go nie zużywają, a w bazie leży tylko jego skrót SHA-256. Konta z 2FA nie dostają takich linków.
- Sesje. Losowy token w ciasteczku. W bazie leży tylko jego skrót SHA-256, więc kopia bazy nie daje zalogowanej sesji. Sesja wygasa po 30 dniach bez użycia, a po zmianie hasła wygasają pozostałe sesje konta.
- Ochrona przed zgadywaniem. Nieudane logowania i błędne klucze są limitowane na adres IP: 10 prób na minutę, potem odpowiedź 429.
- Logowanie przez Google i Microsoft (SSO). Opcjonalne (
[sso] wlaczone): OpenID Connect z kodem autoryzacji i PKCE, kontrolaaud,nonce, wygaśnięcia i zweryfikowanego adresu. Konta nie powstają z SSO: właściciel powiązuje istniejące konto z adresem e-mail dostawcy, a po pierwszym logowaniu zapamiętujemy stały identyfikator dostawcy (zmiana adresu u dostawcy nie przejmuje konta). Można ograniczyć domeny ([sso] domeny). - Klucze API. Klucz pełny i klucz tylko do odczytu, odnawiane w Ustawieniach. Ostatniego aktywnego właściciela nie da się wyłączyć ani zdegradować.
- Klucze integracji. Osobny klucz dla każdej integracji (Zapier, strona WWW): tylko wybrane moduły, tryb odczyt albo zapis, termin ważności i własny limit żądań. W bazie leży wyłącznie skrót SHA-256, a pełną wartość widać raz, przy utworzeniu. Klucz można w każdej chwili cofnąć.
- Role własne i zespoły. Właściciel tworzy role z uprawnieniami per moduł (odczyt albo zapis: szanse, zadania, poczta, finanse, projekty, raporty, zespół, ustawienia) nad rolą bazową handlowca albo odczytu. Zespoły mają kierownika, który widzi i zmienia szanse swoich członków.
- Limity żądań. Poza limitem prób logowania działa limit żądań na adres IP (domyślnie 1200 na minutę) i na konto albo klucz integracji (domyślnie 600 na minutę), z odpowiedzią 429 i nagłówkiem Retry-After.
- Dziennik logowań i urządzenia. Każda próba logowania (także nieudana) zostawia wpis z urządzeniem i adresem IP. Logowanie z nowego urządzenia wysyła powiadomienie (push i alert instancji). Każdy użytkownik widzi swoje urządzenia i wylogowuje je zdalnie, a właściciel robi to dla dowolnego konta.
- Maskowanie w logach. Logi serwera, ślady błędów i zgłoszenia przechodzą przez maskowanie: adresy e-mail, telefony, PESEL, numery rachunków, tokeny, klucze i hasła nie trafiają do logów ani do bazy błędów.
- Rotacja sekretów. Sekrety leżą w katalogu instancji (nie w repozytorium); diagnostyka w panelu przypomina o kluczach starszych niż 90 dni.
- Webhooki. Wejście z formularza wymaga sekretu, a powiadomienia wychodzące są podpisane HMAC-SHA256.
- Powiadomienia push. Ładunek jest szyfrowany kluczem urządzenia (RFC 8291), a serwer wysyła go tylko do znanych usług powiadomień Apple, Google, Mozilli i Microsoftu (https).
Ochrona przed atakami w przeglądarce
Content-Security-Policy: default-src 'self': panel nie ładuje skryptów ani stylów z zewnątrz i nie używa kodu inline.- Nagłówki
X-Content-Type-Options: nosniffiReferrer-Policy: no-referrer. - Treści od klientów (maile, formularze) są zawsze traktowane jako tekst, nigdy jako HTML. Odpowiedzi API mają
Cache-Control: no-store. - Pliki panelu są serwowane z walidacją ścieżki (bez
.., ukrytych plików i dowiązań wychodzących poza katalog).
Dziennik zmian (audyt)
Każda zmiana w danych (POST, PUT, PATCH, DELETE) zostawia wpis: kto (login albo poziom klucza), co (metoda i ścieżka) oraz stan przed i po. Eksport całości danych i eksport danych jednej osoby są audytowane osobno i dostępne tylko dla właściciela. Dziennik nie zawiera haseł ani sekretów. Log dostępu serwera zapisuje czas, metodę, ścieżkę bez parametrów, kod odpowiedzi i adres IP, bez kluczy, sekretów i adresów e-mail.
Kopie zapasowe i ciągłość
- Spójna kopia bazy co godzinę: 48 godzinowych i 14 dziennych na instancję.
- Raz na 7 dni test odczytu najnowszej kopii (kontrola integralności i liczby rekordów), a wynik widać w panelu.
- Kopia dobowa poza kontenerem, na serwerze (retencja 30 dni), oraz lustro na osobnym urządzeniu operatora.
- Monitoring: stan instancji (ok, ostrzeżenie, awaria) liczony w produkcie, a zewnętrzny skrypt co 5 minut sprawdza dostępność i wysyła alert mailowy bez danych osobowych.
- Odtwarzanie z kopii i awarie: runbook awaryjny w dokumentacji administratora.
- Szyfrowanie w spoczynku. Sekrety w bazie (ziarna TOTP) są szyfrowane kluczem poza bazą. Kopie zapasowe można szyfrować (
[szyfrowanie] kopie: pliki.sqlite.fxe, szyfr strumieniowy z HMAC-SHA256 i uwierzytelnienie encrypt-then-MAC, naruszony plik jest odrzucany). Bazę żywą chroni szyfrowanie wolumenu (LUKS albo szyfrowanie dysku u dostawcy): diagnostyka wykrywa dm-crypt albo bierze deklarację z konfiguracji, a[szyfrowanie] wymagaj_wolumenuzamienia brak szyfrowania w alarm. Stan:GET /api/zgodnosc/szyfrowanie.
Obsługa błędów i awarii
- Identyfikator błędu. Każdy błąd serwera (5xx) dostaje numer
E-XXXXXXXX, który wraca w odpowiedzi i w panelu; właściciel otwiera go po numerze (komunikat i ślad bez danych klientów). - Zgłoszenie jednym kliknięciem. Przycisk Zgłoś błąd wysyła ekran, rozdzielczość, numery błędów i krótkie komunikaty JS; treści szans i dane klientów nie są częścią zgłoszenia.
- Błędy w panelu. Błędy JavaScript z przeglądarki trafiają do zbioru z licznikami (jeden wpis na rodzaj błędu, limit żądań na adres IP).
- Strona statusu. Publiczny adres
/status(i/api/status) pokazuje tylko stany składników: panel, baza, importy, kopie, tryb awaryjny; działa także przy niedostępnej bazie. - Diagnostyka. Właściciel widzi w panelu, co nie działa i dlaczego, z radą co zrobić (baza, dysk, spójność, kopie, każdy import, model, poczta, rotacja sekretów).
- Tryb awaryjny. Właściciel jednym przyciskiem przełącza panel w tryb tylko do odczytu na czas naprawy; logowanie i zgłaszanie błędów działają dalej.
- Kopia przed migracją. Gdy aktualizacja zmienia schemat bazy, przed migracją powstaje osobna kopia pliku (zostaje pięć ostatnich).
- Ponawianie importów. Import z błędem jest ponawiany sam z rosnącym odstępem (5, 10, 20, 40, 80 minut); po wyczerpaniu prób zostaje alert.
- Wsparcie i incydenty. FAQ błędów z przyczyną i rozwiązaniem, czat z agentem odpowiadającym z bazy wiedzy, eskalacja do człowieka i mierzony czas reakcji. Planowane przerwy są zapowiadane w panelu (pasek 72 godziny wcześniej) i na stronie
/status, a publiczna historia incydentów nie zawiera retro ani szczegółów wewnętrznych. Po incydentach wysokich i krytycznych właściciel zapisuje retrospekcję z działaniami (termin 5 dni). - Zdrowie aplikacji na telefonach. Aplikacja Dzwonek raportuje stany techniczne (zgoda na powiadomienia, push, sieć, bateria, zegar) bez treści klientów, a właściciel może zlecić diagnostykę konkretnego telefonu.
- Łagodna degradacja. Niedostępna baza daje 503, a niedostępny model, poczta albo sieć 502 z komunikatem; reszta panelu działa dalej. Testy chaosu wyłączają bazę, model i sieć i sprawdzają to przy każdej zmianie.
Minimalizacja i usuwanie danych
- Anonimizacja danych osobowych po czasie bez ruchu (retencja) z zachowaniem liczb do statystyk, włączana per instancja.
- Eksport danych osoby na jej wniosek.
- Zgody RODO z datą, źródłem i dowodem.
- Szczegóły procesów: RODO dla klientów.
Dostęp do danych wrażliwych i lokalizacja
- Dziennik dostępu. Każdy udany odczyt karty szansy z polem wrażliwym (dochód, zobowiązania, BIK, forma dochodu: lista w
[zgodnosc] pola_wrazliwe), dokumentów, załączników, nagrań i eksportów zostawia wpis: kto, kiedy, z jakiego adresu, której szansy i jakiej kategorii, bez treści danych. Retencja 730 dni ([zgodnosc] logi_dni), alert przy masowym odczycie (ponad 50 różnych szans w dobie przez jedno konto). Widzi go tylko właściciel:GET /api/zgodnosc/dostep. - Lokalizacja danych. Deklarowany region i hosting (
[zgodnosc]) zestawiane są z aktywnymi funkcjami, które wysyłają treść poza serwer:GET /api/zgodnosc/lokalizacjaodpowiada, czy dane są w UE i co z nich wychodzi. - Śledzenie maili. Domyślnie wyłączone (
[sledzenie] wlaczone). Gdy jest włączone, każdy śledzony mail niesie informację dla odbiorcy i link wyłączenia śledzenia; zbieramy tylko zliczenia otwarć i kliknięć przy mailu (bez adresu IP i User-Agent), a przekierowania idą wyłącznie na adresy zapisane przy wysyłce. - Portal klienta. Używa jednego niezbędnego pliku cookie sesji (HttpOnly, SameSite=Strict, do 3 dni); informacja o plikach cookie wyświetla się w trzech językach, bez cookie reklamowych i analitycznych.
Modele językowe
Funkcje oparte na modelu językowym są domyślnie wyłączone i włącza je właściciel instancji w porozumieniu z FluxLab. Model dostaje tylko dane potrzebne do zadania (rozmowa jednej szansy), działa w trybie tylko do odczytu, a jego wynik zawsze zatwierdza człowiek. Treść klienta jest traktowana jako dane, nie jako polecenie. Instancja może wymagać zgody klienta, zanim model przetworzy jego dane, każde użycie AI ma wpis w rejestrze przeglądu etycznego, a szkice dla klientów mogą nieść informację o udziale AI. Dostawca: Anthropic, zob. podmioty pomocnicze.
Czego jeszcze nie ma
Mówimy o tym wprost, żeby nie było rozbieżności z oczekiwaniami:
- baza żywa SQLite nie jest szyfrowana na poziomie aplikacji (biblioteka standardowa tego nie umożliwia); chronią ją uprawnienia systemowe i szyfrowanie wolumenu u dostawcy lub LUKS, a szyfrowane są sekrety i, opcjonalnie, kopie zapasowe,
- baza SQLite nie jest szyfrowana na poziomie aplikacji (pliki i sekrety są); ochrona opiera się na uprawnieniach systemowych i zabezpieczeniach dostawcy serwera,
- wysyłka kopii do chmury obiektowej (S3, B2) jest przygotowana w konfiguracji, ale nie jest włączona,
- nie mamy certyfikacji ISO 27001 ani raportu SOC 2,
- passkeys (ES256) nie weryfikują producenta klucza, a SSO przez Google i Microsoft wymaga skonfigurowania klienta OAuth przez FluxLab i powiązania kont,
- nie przeprowadziliśmy niezależnego testu penetracyjnego: plan i zakres są opisane, a każda trasa API przechodzi automatyczny test polityki dostępu (bez logowania 401, konto tylko do odczytu nie zmienia danych).
Zgłaszanie podatności
Podatności zgłaszaj na pawel@fluxlab.pl. Potwierdzamy zgłoszenie w ciągu 2 dni roboczych, a poprawki krytyczne wdrażamy na wszystkich instancjach niezwłocznie. Incydenty dotyczące danych osobowych obsługujemy według procedury 72 godzin (zob. RODO dla klientów).