Produkt jest sprzedawany specjalistom ds. bezpieczeństwa, którzy będą go dokładnie analizować przed zaufaniem mu z danymi compliance swojej organizacji. Trzy zasady kierują każdą decyzją projektową:
1. Awaria zamknięta (fail closed) — z jednym celowym wyjątkiem. Jeżeli mechanizm bezpieczeństwa nie może działać, czynność od niego zależna zostaje odrzucona, a awaria ujawniona; mechanizm nie jest pomijany z ostrzeżeniem. Czynność, której wiersza audytu nie da się zapisać, jest wycofywana razem z nim (krótka lista wyjątków, m.in. nieudane logowania i blokady kont, jest zapisywana w logu zamiast odrzucana — w przypadku logowań dlatego, że odrzucenie wycofałoby też licznik blokady). Żądanie lub zadanie w tle, które potrzebuje Vault do odpakowania klucza swojego Dzierżawcy, kończy się błędem, gdy Vault nie odpowiada (§ 7), a wdrożenie nie startuje, jeżeli Vault zwróci puste sekrety. Kopia zapasowa bazy danych, która nie może dotrzeć do jednego z dwóch dostawców przechowywania, trafia do drugiego, a jej uruchomienie kończy się jako nieudane, nie jako udane. Wyjątkiem jest uszkodzony łańcuch audytu — nie zatrzymuje on Usługi. Codzienna weryfikacja kończy się kodem błędu i wysyła e-mail do aktywnych administratorów Dzierżawcy, którego dotyczy, oraz na adres operacyjny Operatora, a przerwanie jest widoczne dla Dzierżawcy pod /api/audit/verify. Odmowa obsługi karałaby klienta za przerwanie, które już nastąpiło, a weryfikacja dała już kiedyś wynik fałszywie pozytywny. Oprogramowanie compliance, które po cichu degraduje się, jest gorsze niż brak oprogramowania.
2. Baza danych jest granicą bezpieczeństwa. Izolacja dzierżawców jest egzekwowana przez Row-Level Security PostgreSQL na poziomie bazy danych, nie przez filtry warstwy aplikacji. Błąd aplikacji, który zapomni filtrować po dzierżawcy, jest nieszkodliwy: baza danych zwraca zero wierszy.
3. Wykrywalność manipulacji zamiast jej zapobiegania. Architektura sprawia, że każda manipulacja jest wykrywalna i identyfikowalna przez łańcuch skrótów, znaczniki kotwic i endpoint weryfikacji.
Każda tabela powiązana z dzierżawcą ma następujące ustawienia stosowane w czasie migracji:
ALTER TABLE <tabela> ENABLE ROW LEVEL SECURITY;
ALTER TABLE <tabela> FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON <tabela>
USING (tenant_id::text = current_setting('app.current_tenant_id', true));
FORCE ROW LEVEL SECURITY stosuje politykę nawet do właściciela tabeli. Para GUC (app.current_tenant_id, app.current_org_unit_id) jest ustawiana lokalnie dla transakcji, na początku transakcji każdego żądania:
# apps/tenants/context.py (uproszczone)
with connection.cursor() as cur:
cur.execute("SELECT set_config('app.current_tenant_id', %s, true)", [str(tenant.id)])
cur.execute("SET LOCAL ROLE app")
app.current_org_unit_id jest ustawiana w ten sam sposób i tylko dla użytkownika ograniczonego do określonych placówek (§2.3). set_config(…, true) to funkcyjna postać SET LOCAL: wartości GUC wygasną z końcem transakcji. Przed Postgresem nie ma żadnego poolera połączeń, a CONN_MAX_AGE = 0 — każde żądanie otrzymuje własne połączenie, zamykane wraz z końcem żądania. Wartość GUC nie może więc przetrwać poza swoją transakcją z samej konstrukcji: nie istnieje współdzielona sesja, do której mogłaby wyciec.
| Rola | Uprawnienia | Używana przez |
|---|---|---|
| superużytkownik bootstrap (właściciel) | Superużytkownik klastra; właściciel wszystkich tabel; DDL | Migracje Django, nocny zrzut kopii zapasowej i jednorazowe polecenia operatorskie z jawnym opt-in — nigdy w ścieżce żądania |
app | SELECT, INSERT, UPDATE, DELETE na większości tabel biznesowych; NOSUPERUSER NOBYPASSRLS, więc ograniczona przez RLS przy każdym zapytaniu. Nie jednolicie: tam, gdzie ścieżka obsługi żądań nigdy nie może usunąć ani nadpisać wiersza, uprawnienie jest odebrane — app nie może usunąć rekordu samego Dzierżawcy i może aktualizować tylko wskazane jego kolumny (od 2026-09-29), a migracja odbierająca takie uprawnienie przerywa wdrożenie, jeśli uprawnienie przetrwa | Środowisko uruchomieniowe aplikacji (ORM Django, widoki DRF) |
audit_writer | INSERT i SELECT na audit_log; brak UPDATE, DELETE i TRUNCATE | Serwis audytowy |
superadmin | BYPASSRLS NOSUPERUSER NOLOGIN; SELECT na wyselekcjonowanej liście tabel, bez INSERT, UPDATE, DELETE i TRUNCATE na jakiejkolwiek tabeli — wyłącznie odczyty | Odczyty międzydzierżawcze konsoli operatora i narzędzia łańcucha audytowego |
platform_writer | BYPASSRLS NOSUPERUSER NOLOGIN; INSERT/UPDATE na wyselekcjonowanej liście tabel; żadnych uprawnień do audit_log | Zapisy konsoli operatora |
iam_definer | BYPASSRLS NOSUPERUSER NOINHERIT NOLOGIN; właściciel dwóch funkcji wyszukiwania między Dzierżawcami (SECURITY DEFINER); SELECT na tenant_membership oraz SELECT/UPDATE na poziomie kolumn (trzy/jedna kolumna) w iam_password_reset_token | Nigdy nie jest przyjmowana przez sesję: osiągalna wyłącznie jako właściciel tych dwóch funkcji |
app_priv | Rola logowania, bez uprawnień superużytkownika, NOBYPASSRLS; dziedziczy app i może przechodzić (wyłącznie SET ROLE) do superadmin, platform_writer i audit_writer; bez własnych uprawnień poza tymi | Worker zadań w tle i konsola operatora — nigdy backend obsługujący żądania |
app_ro | NOLOGIN NOSUPERUSER NOBYPASSRLS; wyłącznie SELECT i tylko na tabelach, które czyta sama rola app; nie należy do żadnej roli; podlega tym samym politykom wierszowym, w tym FORCE | Odczyty tylko do odczytu w układzie federacji (nadzorca JST); osiągana wyłącznie przez SET ROLE z app |
Druga oś izolacji — org_unit_id — rozdziela placówki (Site) jednego Dzierżawcy, np. szkoły w Dzierżawcy JST. Każda placówka ma własną jednostkę organizacyjną, nadawaną przez bazę danych: wyzwalacz na tabeli site (site_org_unit_guard, tenants/0066) tworzy jednostkę przy utworzeniu placówki (przy aktualizacji — tylko dla placówki, która jeszcze jej nie ma), odrzuca jednostkę podaną przez wywołującego i nie pozwala jej później zmienić; jednostka należy wyłącznie do jednej placówki, a od tenants/0070 kolumna jest NOT NULL.
RLS egzekwuje jednostkę na sześciu tabelach: site, exception, control_programs_controlassignment, nis2_control_task, nis2_self_identification i incident, każda z polityką RESTRICTIVE org_unit_scope. Na każdej z nich poza site jednostkę wiersza ustawia baza danych na podstawie jego placówki: wyzwalacz BEFORE INSERT OR UPDATE (set_org_unit_from_site(), a dla przypisań zadań kontrolnych set_org_unit_from_target_site()) nadpisuje org_unit_id przy każdym zapisie i odrzuca placówkę, której zapisujący nie widzi, albo placówkę innego Dzierżawcy, a ograniczenie CHECK odrzuca wiersz, który wskazuje placówkę, ale nie ma jednostki. Wiersz bez placówki — wyjątek, zadanie, samoidentyfikacja lub incydent dotyczący całej organizacji — nie ma jednostki i baza danych z założenia pokazuje go w każdej jednostce. Aplikacja jest tu surowsza dla wyjątków i przypisań wymagań kontrolnych: użytkownik ograniczony do placówek nie widzi ani wyjątków poziomu organizacji, ani przypisań skierowanych do wszystkich placówek.
Kto jest ograniczany. app.current_org_unit_id przechowuje listę jednostek rozdzielonych przecinkami, więc użytkownik odpowiedzialny za dwie szkoły jest ograniczony do obu. Zmienna jest ustawiana dla każdego użytkownika, którego wszystkie role u Dzierżawcy są ograniczone do placówek — role nadane w placówce oraz wiersz roli itso o zasięgu całego Dzierżawcy, który poza Dzierżawcami JST oznacza placówki wskazujące tego użytkownika jako ITSO — na jednostki tych placówek. Członek Dzierżawcy bez roli albo bez placówki, do której ma dostęp (w Dzierżawcy JST wiersz roli itso lub school_director o zasięgu całego Dzierżawcy nie daje żadnej) otrzymuje wartość-strażnika, której nie nosi żadna jednostka, i widzi tylko wiersze poziomu organizacji. Użytkownik z dowolną inną rolą o zasięgu całego Dzierżawcy (tenant_admin, auditor i in., a poza Dzierżawcami JST także school_director o zasięgu całego Dzierżawcy) nie jest zawężany przez tę politykę — celowo.
Najpierw kontrola w kodzie aplikacji, pod nią zabezpieczenie w bazie danych. Odczyty i zapisy wyjątków, Zadań Kontrolnych NIS2, rekordów samoidentyfikacji i incydentów są ograniczane do placówek, w których użytkownik ma uprawnienie właściwe dla danej czynności (sites_granted), a zapis jest sprawdzany w placówce samego wiersza — przy przeniesieniu także w nowej. W Dzierżawcy JST role itso i school_director nadaje się wyłącznie per placówka: wiersz którejkolwiek z nich o zasięgu całego Dzierżawcy nie daje dostępu do żadnej placówki, a wskazanie kogoś jako ITSO placówki niczego mu nie nadaje.
Czego baza danych jeszcze nie rozdziela per placówka: ryzyk, aktywów, stanów zgodności kontroli i ich dowodów oraz powiązań dostawców z placówkami. Ryzyka i aktywa (od 2026-10-21) oraz stany zgodności są ograniczane per placówka wyłącznie w kodzie aplikacji.
Koordynator JST (posiadający rolę na poziomie JST) widzi zagregowany pulpit wszystkich instytucji w ramach JST. Izolację między Dzierżawcami egzekwuje RLS, a nie kod aplikacji; szkoła założona jako osobny Dzierżawca jest więc odizolowana od każdej innej szkoły przez RLS.
auth.login.failed, auth.lockout). Nic więcej nie dzieje się automatycznie: blokada nie wywołuje alertu ani nie banuje adresu IP.Passkeys WebAuthn (FIDO2) są dostępne na wszystkich kontach. Obsługiwane uwierzytelniające: platformy (Touch ID, Windows Hello, biometria Android) i sprzętowe klucze FIDO2 (YubiKey). Walidacja licznika logowań przy każdej asercji; spadł licznika wyzwala automatyczne unieważnienie i alert.
Konfiguracja per Dzierżawca. Obsługiwane przepływy: wyłącznie Authorization Code z PKCE. Walidacja tokenu: podpis (JWKS), iss, aud, exp, nbf, nonce. Preset „Zaloguj się przez Microsoft” (Entra ID / Azure AD).
Konfiguracja per Dzierżawca przez przesłanie metadanych XML IdP. SSO inicjowane przez SP; SSO inicjowane przez IdP domyślnie wyłączone. Mapowanie atrybutów konfigurowalne (e-mail, imię i nazwisko, rola). Provisioning JIT z mapowaniem odkładań grupowych na role.
Sesje po stronie serwera przechowywane w Redis (nie w bazie danych i nie w pliku cookie, który zawiera wyłącznie identyfikator sesji). Atrybuty cookie: Secure; HttpOnly; SameSite=Lax. Sesja unieważniana po stronie serwera przy wylogowaniu. Sesja kończy się w chwili, która nastąpi najwcześniej: wylogowanie, 15 minut bez żadnego żądania (domyślny limit bezczynności; administratorzy Dzierżawcy mogą go ustawić w zakresie 5–480 minut, w każdym planie) albo 8 godzin od zalogowania — aktywność nie wydłuża tego limitu.
exception.approve).tenant_admin, submitter, approver, auditor, iod, a w module NIS2 także itso, school_director i jst_head. Uprawnienia są nadawane wyłącznie przez role.Rola Postgres audit_writer ma INSERT i SELECT na audit_log oraz brak UPDATE, DELETE i TRUNCATE. Test regresyjny CI sprawdza dokładny zestaw uprawnień przy każdym pull requeście — jest to bramka scalania, a nie kontrola w chwili wdrożenia.
row_hash_n = sha256( prev_hash_(n-1) || canonical(row_n) )
Gdzie canonical = dwanaście pól składowych, zserializowanych przez Postgres do tekstu i połączonych znakiem \x1f (ASCII unit separator), w kolejności: tenant_id, actor_user_id, actor_ip, actor_user_agent, action, target_type, target_id, before, after, request_id, at, contract_version. timestamptz::text renderowany jest przy TimeZone = 'UTC' i DateStyle = 'ISO, MDY' (ustawionych na stałe w wyzwalaczu), a połączony tekst haszowany jest jako bajty UTF-8. Aby odtworzyć łańcuch zapytaniem SQL lub z danych wyeksportowanych z Postgresa, ustaw wcześniej te same TimeZone, DateStyle oraz client_encoding = 'UTF8'. Wersją listy pól jest contract_version, przechowywana w każdym wierszu i wchodząca do postaci kanonicznej jako jej dwunaste pole.
GET /api/audit/verify przechodzi przez pełny łańcuch skrótów dla żądającego Dzierżawcy i zwraca:
{
"ok": true,
"total_rows": 47291
}
To jest całe ciało odpowiedzi dla nieuszkodzonego łańcucha. Gdy łańcuch jest uszkodzony, endpoint zwraca 422 z ok: false oraz polami broken_at i reason. Do 2026-09-14 przykład pokazywał także first_row_at, last_row_at, last_anchor_at i schema_versions_seen — żadne z tych pól nie istnieje.
Zadanie Celery beat uruchamiane codziennie (02:20 UTC) rejestruje bieżący row_hash najnowszego wiersza per Dzierżawca oraz łańcucha płaszczyzny sterowania w tabeli audit_chain_anchor, razem z tokenem znacznika czasu RFC 3161 nad tym skrótem, wystawionym przez zewnętrzny urząd znaczników czasu (TSA) — domyślnie freetsa.org (zob. sekcja 13); drugie zadanie, codziennie o 02:40 UTC, weryfikuje token każdej nowej kotwicy — jego podpis i łańcuch zaufania względem przypiętego certyfikatu głównego TSA — jeden raz, przy pierwszym uruchomieniu po jej utworzeniu (do 2026-09-22 zadanie to działało co tydzień). Wcześniejsze kotwice nie są ponownie sprawdzane według harmonogramu: operator sprawdza je wszystkie na żądanie (verify_anchors --all). Pełne przejście łańcucha odbywa się codziennie. Rola superadmin ma na tej tabeli wyłącznie SELECT.
Na zweryfikowane żądanie usunięcia (art. 17 RODO) rekord użytkownika otrzymuje znacznik (deleted-<uuid>@tombstone.invalid). Wiersze audytu przechowują actor_user_id, a nie adres e-mail, więc widok wiersza historycznego pokazuje znacznik bez przepisywania jakiegokolwiek wiersza — łańcuch skrótów jest zachowany z samej konstrukcji. Pola actor_ip i actor_user_agent nie są pseudonimizowane i pozostają przez cały okres istnienia konta Dzierżawcy.
Adresy e-mail w wierszach audytu zapisanych przed wdrożeniem produkcyjnym z 2026-10-12. Do wdrożenia produkcyjnego z 2026-10-12 część czynności zapisywała adres e-mail także w samym wierszu audytu — w polach before / after, a przy próbach logowania i resetu hasła w target_id. Wiersze audytu zapisane przed wdrożeniem produkcyjnym z 2026-10-12 (wydanie scalono 2026-10-11, do produkcji trafiło 2026-10-12) przy następujących czynnościach mogą więc zawierać adresy e-mail: auth.login.failed, auth.login.failure, auth.lockout, auth.saml.user_jit_provisioned, iam.password_reset.requested_unknown, iam.password_reset.no_tenant_context, iam.password_reset.suppressed_rate, iam.platform_admin.granted, iam.platform_admin.revoked, superadmin.tenant.create, platform_admin.impersonate.requested, platform_admin.impersonate.approved, platform_admin.impersonate.denied, platform_admin.impersonate.break_glass_declared, platform_admin.impersonate.break_glass_vetoed, platform_admin.impersonate.emergency_declared, platform_admin.impersonate.end, każda platform_admin.impersonate.action.*, platform_admin.tenant.suspend, platform_admin.tenant.restore, platform_admin.audit_search, audit.chain.discontinuity.declared, nis2.control_task.delegated, vendor.assessment.create, vendor.assessment.invite_sent, vendor.assessment.invite_failed, vendor.assessment.reviewers_notified, controls.assignment.notified, controls.due_reminded, controls.overdue_reminded, exception.expiry_reminded, report.quarterly_dispatched, ops.email_path.ok, ops.email_path.failed, tenant.profile.update, email.graph.connection_created, email.graph.connection_renewed, ops.pitr.drill.success, ops.pitr.drill.failed oraz, wyłącznie w dzierżawie demonstracyjnej, report.scheduled.created. Adres może się też znajdować w tekście swobodnym wiersza dowolnej czynności zapisanego przed tą datą — w komunikacie błędu, w opisie błędu od dostawcy tożsamości, w uzasadnieniu lub komentarzu wpisanym przez użytkownika. Wiersze te pozostają przez cały okres istnienia konta Dzierżawcy — w łańcuchu warstwy sterującej platformy przez cały okres istnienia platformy — na tej samej podstawie prawnej co actor_ip: uzasadniony interes w zakresie integralności audytu (art. 6 ust. 1 lit. f RODO). Ich przepisanie zniszczyłoby łańcuch skrótów, dlatego są ujawniane, a nie usuwane.
Od wdrożenia produkcyjnego z 2026-10-12 w treści ani w polu obiektu docelowego wiersza audytu nie jest zapisywany żaden tekst w postaci adresu e-mail. Osoba mająca rekord użytkownika jest identyfikowana wewnętrznym identyfikatorem. Każdy inny adres (kontakt zewnętrznego dostawcy, identyfikator wpisany w formularzu logowania lub resetu hasła) jest zapisywany jako odnośnik kluczowany: hmac-sha256:k0:, a po nim 128 bitów HMAC-SHA-256 pod kluczem platformy k0. Ponadto każdy wiersz przechodzi przez jedną ścieżkę zapisu, która przed zapisaniem zastępuje odnośnikiem każdy pozostały tekst w postaci adresu — w komunikacie błędu, w błędzie dostawcy tożsamości, w tekście wpisanym przez użytkownika. „W postaci adresu” oznacza nazwa@domena.tld lub nazwa@localhost; adres zapisany w inny sposób nie jest rozpoznawany. Do wdrożenia produkcyjnego poprawki z 2026-10-13 to zastępowanie mogło pozostawić fragment: gdy adresy lub domeny były połączone znakiem @ bez niczego pomiędzy (x@a.com@b.org), w wierszu zostawał odnośnik, po nim @ i nazwa domeny (hmac-sha256:k0:…@b.org) — nigdy cały adres. Wiersze zapisane od wdrożenia produkcyjnego z 2026-10-12 do wdrożenia produkcyjnego zawierającego tę poprawkę mogą zawierać taki fragment; są ujawniane, a nie usuwane. Od poprawki adres zachowuje własny odnośnik, a reszta tekstu połączonego znakami @ staje się drugim odnośnikiem. Do wdrożenia produkcyjnego poprawki z 2026-10-14 pozostawała druga luka: adres zawierający łączące znaki diakrytyczne — literę z akcentem zapisaną jako litera podstawowa i osobny znak (Unicode NFD) albo pismo, w którym samogłoski są takimi znakami, np. dewanagari lub tajskie, albo niewidoczny łącznik (ZWNJ/ZWJ) używany w nazwach perskich i indyjskich — nie był rozpoznawany w całości, więc wiersz mógł zachować go, w całości lub w części, jawnym tekstem. Wiersze zapisane przed wdrożeniem produkcyjnym zawierającym tę poprawkę mogą zawierać taki adres; są ujawniane, a nie usuwane. Od tej poprawki znak łączący lub łącznik jest częścią adresu, a zapis złożony i rozłożony tego samego adresu dają jeden odnośnik. Inne dane osobowe w tekście swobodnym — imię i nazwisko lub numer telefonu wpisane w uzasadnienie albo komentarz — są zapisywane tak, jak je wpisano, i pozostają jak reszta wiersza. Równe adresy dają równe odnośniki, więc powtarzane próby wobec jednego identyfikatora nadal dają się powiązać. Odnośnik jest pseudonimem i pozostaje daną osobową: każdy, kto może spowodować zapisanie przez platformę wybranego adresu i odczytać wiersz, albo kto posiada klucz platformy, może potwierdzić odgadnięty adres. Nie obejmuje to ciągu user-agent sesji wykonującej czynność: jest on zapisywany tak, jak go przesłał klient, a niektórzy klienci, zwłaszcza roboty indeksujące, umieszczają w nim adres kontaktowy.
Strict-Transport-Security: max-age=31536000; includeSubDomains — bez dyrektywy preload; domena nie jest zgłoszona na listę preload przeglądarek.platform-iam); zob. §13.1.Wszystkie odpowiedzi API zawierają nagłówki: Strict-Transport-Security (max-age=31536000; includeSubDomains, bez preload), Content-Security-Policy (`script-src 'self'`, bez `unsafe-inline`; nie oparty na nonce — API nie serwuje skryptów inline), X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Resource-Policy: same-origin, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy: camera=(), geolocation=(), microphone=(), payment=() (te cztery funkcje są wyłączone dla każdego źródła; pozostałe zachowują ustawienia domyślne przeglądarki). Cross-Origin-Resource-Policy i Permissions-Policy są wysyłane od 2026-10-12. Nagłówek Cross-Origin-Embedder-Policy nie jest wysyłany: dotyczy dokumentów, a API serwuje JSON i pliki do pobrania.
/etc/exceptao/, SecretID w trybie 0400), montowanymi tylko do odczytu w kontenerach aplikacji; tokeny krótkotrwałe (1 h, najwyżej 4 h). Minimalne uprawnienia między usługami nie są jeszcze wdrożone: każdy kontener aplikacji, w tym kontener webowy wystawiony do internetu, loguje się jedną wspólną rolą AppRole, która może odczytać całą ścieżkę KV marki — także hasło superużytkownika bazy, hasło uprzywilejowanego loginu bazy, poświadczenia repozytoriów kopii zapasowych i poświadczenie automatycznego otwierania Vault. Środowisko kontenera webowego otrzymuje tylko sekrety zadeklarowane przez jego usługę, ale jego poświadczenie Vault mogłoby pobrać pozostałe. Planowane są osobne ścieżki Vault dla każdej usługi.platform-iam, a aplikacja odpakowuje raz na proces, przy pierwszym użyciu — nie przy każdym żądaniu. Od wydania R3b (2026-10-11) żadne poświadczenie Dzierżawcy nie jest szyfrowane kluczem ogólnoplatformowym. (Jeden wyjątek: każde zapisanie TOTP odczytuje KV, by sprawdzić, czy istnieje platformowy klucz TOTP, a przy niedostępnym Vault kończy się odmową.) Od włączenia kluczy Dzierżawców 2026-10-10 (P0-1, wydanie R2 — §13.1) żądanie lub zadanie w tle odczytujące zaszyfrowane poświadczenie prosi silnik tranzytowy Vault o odpakowanie klucza swojego Dzierżawcy, przechowywanego w pamięci na czas tego żądania lub zadania i przez najwyżej 60 sekund, więc Vault jest zależnością takich żądań i zadań. Jeżeli Vault nie odpowie, żądania te kończą się błędem (fail closed), nigdy nie sięgając po klucz ogólnoplatformowy (§13.1, ograniczenie 7).gitleaks działa w CI przy każdym commicie i jako hook pre-commit.cloudflared utrzymuje wychodzące połączenie mTLS.ui = false), Vault nie publikuje portu poza siecią kontenerów serwera, a Grafana ani inny interfejs monitoringu nie jest wdrożony.deploy/vault-escrow-drill.sh <marka> --apply), uruchamiany ręcznie przez operatora i bez harmonogramu: odtwarza zapisaną migawkę Vault w jednorazowej instancji Vault, potwierdza jej automatyczne otwarcie i odczytuje z powrotem materiał kluczowy. Zakończył się powodzeniem na obu maszynach 2026-09-23 i ponownie 2026-09-24, za drugim razem łącznie z etapem silnika transit.requirements.txt ze skrótami SHA-256. Zależności npm — package-lock.json. Obrazy Docker bazowe referencjonowane przez digest.pip-audit, npm audit, Trivy, Semgrep, gitleaks — w CI przy każdym pull requeście i każdym wypchnięciu do main oraz co tydzień według harmonogramu (w pull requeście npm audit działa tylko przy zmianie pliku zależności frontendu).| Ważność | Wynik CVSS | SLA naprawy |
|---|---|---|
| Krytyczna | ≥ 9,0 | 24 godziny od potwierdzonej podatności |
| Wysoka | 7,0–8,9 | 7 dni |
| Średnia | 4,0–6,9 | 30 dni |
| Niska | < 4,0 | Następne wydanie mniejsze |
Skoordynowane ujawnianie: security@parakscol.pl. .well-known/security.txt opublikowany na wszystkich domenach marki. Zewnętrzny test penetracyjny planowany przed pierwszym płatnym klientem.
Udokumentowany podręcznik IR: wykrywanie i triage, izolacja, eliminacja, odtwarzanie, post-mortem i powiadamianie regulacyjne.
| Jurysdykcja / prawo | Powiadomienie organu nadzorczego | Powiadomienie osób dotkniętych |
|---|---|---|
| RODO / UE | ≤ 72 godziny od powzięcia wiadomości (art. 33) | Bez zbędnej zwłoki dla naruszeń wysokiego ryzyka (art. 34) |
| Polska (KSC / NIS2) | 24 h wczesne ostrzeżenie; 72 h zgłoszenie; sprawozdanie końcowe w ciągu miesiąca od zgłoszenia 72 h (art. 23 NIS2) | Zgodnie z obowiązującym prawem |
Obowiązek Operatora powiadamiania Administratora (Dzierżawcy) o naruszeniu: ≤ 72 godziny, zgodnie z DPA §8.
Wierzymy w nienadmiarowe twierdzenia. Poniższa lista jest uczciwa w kwestii luk.
| Element | Status | Plan |
|---|---|---|
| Certyfikacja SOC 2 Type II | Jeszcze nie — zaangażowanie audytorskie nie rozpoczęte | Architektura zaprojektowana pod kątem gotowości SOC 2. Zaangażowanie audytora planowane po fazie GA. |
| Certyfikacja ISO 27001 | Jeszcze nie | Planowana po SOC 2 Type II. |
| FedRAMP | Nie dotyczy w v1 | Brak klientów federalnych USA w v1. |
| Zewnętrzny urząd znaczników czasu RFC 3161 | W użyciu od 2026-09-22 (freetsa.org); komercyjny TSA jeszcze nieobsługiwany | Codzienne zadanie (02:20 UTC) kotwiczy głowę łańcucha audytowego każdego Dzierżawcy oraz głowę łańcucha płaszczyzny sterowania w zewnętrznym urzędzie znaczników czasu RFC 3161 — domyślnie freetsa.org, bezpłatnej publicznej usłudze prowadzonej przez podmiot trzeci — a każdy nowy token jest weryfikowany jeden raz, przy najbliższym codziennym uruchomieniu (02:40 UTC), względem przypiętego certyfikatu głównego TSA; wcześniejsze tokeny są sprawdzane ponownie na żądanie (sekcja 5.4). TSA otrzymuje wyłącznie skrót SHA-256 głowy łańcucha: żadnego wiersza audytu ani danych osobowych, dlatego nie figuruje na liście podwykonawców (/legal/subprocessors), która obejmuje usługi otrzymujące dane osobowe. Token znacznika czasu dowodzi, kiedy głowa łańcucha istniała, a nie kto ją zapisał. Jeszcze niezrealizowany jest komercyjny TSA: weryfikator obsługuje obecnie wyłącznie odpowiedzi freetsa.org. |
| Cykliczny zewnętrzny test penetracyjny | Pierwszy test planowany przed pierwszym płatnym klientem | Roczna kadencja po osiągnięciu odpowiednich przychodów. |
| Program bug bounty | Jeszcze nie | Faza 2/3 — Intigriti lub HackerOne. |
| Warm-standby DR (RTO ≤ 30 min) | Jeszcze nie — obecny RTO ≤ 4 godziny | Faza 3 — wymaga repliki hot-standby i automatycznego przełączania awaryjnego. |
| Zdalny atest LUKS | Automatyczne otwieranie: wdrożone (LUKS2 + Clevis/Tang NBDE). Zdalny atest: nieoferowany. | Wolumin odblokowuje się przy starcie z dwóch niezależnych serwerów Tang — nic nie jest wpisywane i na maszynie nie ma materiału klucza. Awaryjnie służy hasło deponowane offline; zweryfikowane podczas provisioningu przez zablokowanie Tang zaporą i potwierdzenie, że rozruch zatrzymuje się do czasu jego podania. Nie stosujemy zdalnego atestu stanu rozruchu opartego o TPM. |
Stan na 2026-10-11. Klucze szyfrowania danych (DEK) przypisane do poszczególnych Dzierżawców są używane w obu wdrożeniach produkcyjnych od 2026-10-10 (wydanie R2 w ramach P0-1): każdy aktywny Dzierżawca ma własny klucz, opakowany kluczem przypisanym mu w silniku tranzytowym Vault, a poświadczenie Dzierżawcy zapisane od tego dnia jest szyfrowane tym kluczem. W chwili włączenia kluczy żaden Dzierżawca nie miał zapisanego poświadczenia integracji ani logowania, więc nie było czego migrować. Od 2026-10-11 (wydanie R3b, oba wdrożenia produkcyjne) klucz Dzierżawcy jest jedyną ochroną jego poświadczeń: kopie pod kluczem ogólnoplatformowym, zachowywane do tego dnia, zostały usunięte, a klucz ogólnoplatformowy, który je szyfrował, nie jest już ładowany przez aplikację ani przechowywany w bieżących sekretach Vault. Kryptograficzne usuwanie („crypto-shredding”) poświadczeń Dzierżawców obowiązuje zatem od 2026-10-11, w granicach opisanych poniżej. Sekrety TOTP nie są objęte kluczami Dzierżawców (ograniczenie 5).
Dlaczego nie czekaliśmy 30 dni. Projekt przewidywał, że harmonogram zacznie obowiązywać 30 dni (okres retencji kopii zapasowych) po wydaniu R3, ponieważ kopia zapasowa wykonana przed R3 mogłaby zawierać kopię poświadczenia pod kluczem ogólnoplatformowym. Tu żadna jej nie zawiera, bo takiej kopii nigdy nie utworzono: w chwili włączenia kluczy każda kolumna poświadczeń była pusta, a testy odtworzenia z 2026-10-11, wykonane na ostatnich kopiach zapasowych sprzed R3b, również wykazały puste wszystkie kolumny poświadczeń. Operator uznał więc 2026-10-11 to oczekiwanie za zbędne. Gdyby takie kopie istniały, oczekiwanie obowiązywałoby.
Harmonogram usunięcia — obowiązuje od 2026-10-11. Kryptograficzne usunięcie może zostać zlecone wyłącznie dla zarchiwizowanego Dzierżawcy. Zlecenie jest wstrzymywane na 30-dniowy okres karencji, w którym można je anulować; po jego upływie klucz Dzierżawcy jest niszczony. Zaszyfrowane migawki Vault wykonane przed tą chwilą są przechowywane do 30 dni (VAULT_SNAPSHOT_RETENTION_DAYS, domyślnie 30 — to samo okno co dla kopii zapasowych bazy danych), a odtworzenie migawki w tym oknie przywróciłoby klucz. Usunięcie staje się więc po naszej stronie nieodwracalne około 60 dni od potwierdzonego zlecenia: 30 dni karencji, a następnie do 30 dni przechowywania zaszyfrowanych kopii zapasowych i migawek Vault. Obejmuje zapisane poświadczenia Dzierżawcy (sekrety logowania, poczty i integracji); nie obejmuje sekretów TOTP ani łańcucha audytowego (ograniczenia 2 i 5).
Dziesięć ograniczeń, które publikujemy, aby nikt nie przypisywał kryptograficznemu usuwaniu więcej, niż ono zapewnia:
/api/audit/verify nadal go weryfikuje. Zapis o usunięciu przetrwa same dane. Usunięcie danych pseudonimizuje rekord użytkownika, do którego wiersze audytu odwołują się wewnętrznym identyfikatorem; same wiersze audytu nigdy nie są przepisywane (§5.5).platform-iam (§6.2), więc crypto-shred Dzierżawcy ich nie obejmuje; usunięcie użytkownika usuwa jego sekret.IAM_OIDC_KEY), oraz dawny klucz sekretów TOTP (IAM_TOTP_KEY) zniknęły z aplikacji i z bieżących sekretów Vault w obu wdrożeniach (2026-10-11). Tego samego dnia wszystkie starsze wersje magazynu klucz-wartość Vault, które nadal przechowywały którykolwiek z nich, zostały trwale zniszczone, a Operator usunął swoje kopie depozytowe offline obu kluczy. Jedyne kopie, które można przywrócić, znajdują się w zaszyfrowanych nocnych migawkach Vault wykonanych wcześniej i przechowywanych przez najwyżej 30 dni, więc ostatnia z nich wygaśnie około 2026-11-10. Żaden szyfrogram pod kluczem IAM_OIDC_KEY nie istnieje, więc jego kopie niczego nie odszyfrowują. Kopie zapasowe bazy danych wykonane przed migracją klucza TOTP (P0-1, faza 6, 2026-10-11) zawierają sekrety TOTP zaszyfrowane kluczem IAM_TOTP_KEY; odtworzenie migawki Vault wykonanej przed usunięciem klucza pozwoliłoby je ponownie odczytać, dopóki same te kopie zapasowe nie wygasną po 30 dniach.| Cel | Kontakt |
|---|---|
| Ujawnianie luk bezpieczeństwa | security@parakscol.pl |
| Prywatność / DPA | iod@parakscol.pl |
.well-known/security.txt | Opublikowany na każdej domenie marki |
| Klucz publiczny PGP | Opublikowany pod /.well-known/pgp-key.asc na wszystkich trzech domenach marek; odcisk 9E3CDE9EB1891E03334081A66AACF8B73B629008 |