Ocena bezpieczeństwa, utrzymywalności i stanu bieżącego przed etapem skalowania
Czy platforma jest bezpieczna tam, gdzie są pieniądze i dane?
Częściowo. Sama płatność jest poprawnie oddelegowana do Stripe Checkout — dane kart nigdy nie
dotykają Państwa serwera i to dobra decyzja architektoniczna. Natomiast potwierdzanie płatności
opiera się na powrocie użytkownika do aplikacji, a nie na webhooku (ustalenie K-1). W praktyce
oznacza to, że zamówienie może zostać opłacone i nigdy nie zrealizowane, jeśli klient zamknie kartę
po zapłacie. Odwrotny scenariusz — realizacja bez płatności — jest przy obecnej konfiguracji mniej
prawdopodobny, ale nie wykluczony.
Czy da się ją utrzymać i odtworzyć bez autorów?
Dziś nie w rozsądnym czasie. Kod jest czytelny, ale nie istnieje odtwarzalny opis środowiska
(ustalenie W-2): brakuje listy wymaganych zmiennych, opisu zależności zewnętrznych i procedury
postawienia systemu od zera. Odtworzenie produkcji przez nowy zespół oszacowałem na 3–5 dni pracy
z dostępem do infrastruktury, głównie na odgadywanie konfiguracji. Po wdrożeniu rekomendacji z
rozdziału 5 ten czas schodzi do kilku godzin.
Jaki jest stan na dziś?
Platforma jest zdrowa jak na system zbudowany szybko i działający produkcyjnie. Nie znalazłem
śladów włamania, wycieku danych ani rażących zaniedbań. Znalazłem 2 ustalenia krytyczne, 3 wysokie,
5 średnich i 6 niskich. Żadne z nich nie wymaga zatrzymania działalności, ale dwa krytyczne
powinny zostać naprawione przed zwiększeniem ruchu, bo ich koszt rośnie proporcjonalnie do liczby
transakcji.
| Nr | Ustalenie | Waga | Obszar | Szacowany nakład naprawy |
|---|---|---|---|---|
| K-1 | Realizacja zamówienia zależna od powrotu użytkownika, webhook Stripe bez obsługi idempotencji | krytyczne | Płatności | 2–3 dni |
| K-2 | Brak izolacji danych między kontami na poziomie zapytań (autoryzacja tylko w warstwie UI) | krytyczne | Bezpieczeństwo | 4–6 dni |
| W-1 | Klucze API w zmiennych środowiskowych bez rotacji i bez rozdzielenia środowisk | wysokie | Bezpieczeństwo | 1 dzień |
| W-2 | Brak odtwarzalnego opisu środowiska i procedury wdrożenia | wysokie | Utrzymanie | 2 dni |
| W-3 | Kopie zapasowe bazy wykonywane, ale nigdy nie testowane odtworzeniem | wysokie | Ciągłość | 1 dzień |
| S-1…S-5 | Brak indeksów na kolumnach filtrowanych, zapytania N+1 w panelu, logi bez korelacji, brak monitoringu błędów, migracje bez wersjonowania | średnie | Wydajność, utrzymanie | 5–7 dni łącznie |
| N-1…N-6 | Zależności z zaległymi aktualizacjami, martwy kod, brak testów ścieżki płatności, niespójne nazewnictwo, brak README, TODO w kodzie produkcyjnym | niskie | Jakość | ciągłe |
Zamówienie zmienia status na opłacone w handlerze strony powrotu (/checkout/success),
po odczytaniu identyfikatora sesji Stripe z adresu. Webhook checkout.session.completed
istnieje, ale zapisuje wyłącznie log — nie zmienia stanu zamówienia. Handler nie sprawdza też,
czy dane zdarzenie było już przetwarzane.
Dwa niezależne scenariusze:
// app/checkout/success/page.tsx (fragment, zanonimizowany)
const session = await stripe.checkout.sessions.retrieve(searchParams.session_id)
if (session.payment_status === 'paid') {
await db.order.update({ where: { id: session.metadata.orderId },
data: { status: 'PAID' } }) // ← jedyne miejsce zmiany statusu
}
// app/api/webhooks/stripe/route.ts (fragment)
case 'checkout.session.completed':
logger.info('checkout completed', { id: event.id }) // ← brak zmiany stanu
break
processed_events z event.id jako kluczem głównym i przetwarzać
zdarzenie w transakcji z zapisem tego identyfikatora — powtórka nie zrobi wtedy nic.stripe-signature (obecnie weryfikowany poprawnie — to zostaje bez zmian).Zapytania do bazy w kilku endpointach pobierają rekord po jego identyfikatorze, a przynależność do konta sprawdzana jest dopiero przy renderowaniu widoku. W trzech miejscach (eksport danych, pobranie faktury, szczegóły zamówienia) tej weryfikacji nie ma wcale — wystarczy podmienić identyfikator w adresie.
To najprostsza do wykorzystania klasa podatności: nie wymaga narzędzi, wystarczy zmiana cyfry w URL. Przy danych klientów i fakturach oznacza ryzyko naruszenia ochrony danych osobowych ze wszystkimi tego konsekwencjami, łącznie z obowiązkiem zgłoszenia.
// app/api/invoices/[id]/route.ts (fragment, zanonimizowany)
const invoice = await db.invoice.findUnique({ where: { id: params.id } })
return Response.json(invoice) // ← brak filtra po organizationId
Sprawdzone wyłącznie przez lekturę kodu; zgodnie z ustaleniami nie wykonywałem żadnych zapytań do środowiska produkcyjnego.
Rzetelny raport mówi też, gdzie kończy się jego zasięg:
| Kiedy | Co | Dlaczego w tej kolejności |
|---|---|---|
| Najbliższe 2 tygodnie | K-1, K-2 | Koszt obu rośnie wprost proporcjonalnie do ruchu; naprawa przed skalowaniem jest kilkukrotnie tańsza niż po. |
| Miesiąc | W-1, W-2, W-3 | Zdejmują zależność od autorów systemu i ryzyko utraty danych. W-2 jest warunkiem sensownego onboardingu nowego zespołu. |
| Kwartał | S-1…S-5 | Wydajność i obserwowalność — potrzebne, gdy ruch rośnie, ale nie blokują dziś. |
| Na bieżąco | N-1…N-6 | Higiena kodu, realizowana przy okazji innych prac. |