Asystent AI napisze działający endpoint w kilkanaście sekund. Problem w tym, że „działający” i „bezpieczny” to dwa różne stany, a testy akceptacyjne sprawdzają zwykle tylko ten pierwszy. Kod przechodzi code review, bo robi dokładnie to, co miał robić. Luka wychodzi pół roku później.
Ten artykuł pokazuje, co dokładnie mówią badania o bezpieczeństwie kodu generowanego przez modele językowe, jakie klasy błędów powtarzają się najczęściej i jak wygląda audyt takiego kodu u nas.
Co pokazują dane
Najszerzej zakrojone publiczne badanie na ten temat to GenAI Code Security Report opublikowany przez Veracode 30 lipca 2025 roku. Przetestowano w nim ponad 100 modeli językowych na zadaniach programistycznych w czterech językach: Javie, Pythonie, C# i JavaScripcie.
Wynik: 45% wygenerowanych próbek kodu nie przeszło testów bezpieczeństwa i wprowadzało podatności z listy OWASP Top 10.
W rozbiciu na języki różnice są duże:
| Język | Odsetek próbek z podatnością |
|---|---|
| Java | 72% |
| C# | 45% |
| JavaScript | 43% |
| Python | 38% |
Najgorzej wypadła ochrona przed cross-site scriptingiem — w zadaniach, gdzie XSS (CWE-80) był realnym zagrożeniem, modele nie zabezpieczyły kodu w 86% przypadków.
Najważniejszy wniosek raportu nie dotyczy jednak samych liczb. Veracode zwraca uwagę, że wyniki bezpieczeństwa pozostawały płaskie niezależnie od wielkości modelu i zaawansowania treningu. Nowsze i większe modele piszą kod, który częściej działa poprawnie, ale nie piszą kodu, który jest bezpieczniejszy. To znaczy, że problem nie rozwiąże się sam przez czekanie na kolejną wersję narzędzia.
Dlaczego AI pisze niebezpieczny kod
Nie dlatego, że „nie umie”. Powody są konkretne i każdy z nich jest przewidywalny.
Model uczył się na publicznym kodzie. Większość przykładów w internecie to tutoriale, odpowiedzi ze Stack Overflow i kod demonstracyjny — pisany po to, żeby pokazać mechanizm, a nie żeby wytrzymać produkcję. Kod pokazujący „jak zrobić zapytanie do bazy” rzadko zawiera warstwę autoryzacji.
Model nie zna Twojego modelu zagrożeń. Nie wie, które pole jest publiczne, a które tylko dla zalogowanego kontrahenta, czy ten endpoint stoi za VPN-em i kto realnie może wysłać do niego request. Pisze kod spełniający prompt, nie kod spełniający politykę dostępu, której mu nie opisano.
Model widzi fragment, nie system. Generuje funkcję w oderwaniu od tego, że walidacja miała być w Form Requeście, że to konkretne zapytanie wykona się w pętli po 3000 rekordów, albo że identyczna logika istnieje już w innym serwisie i teraz będą się rozjeżdżać.
Kod wygląda na pewny siebie. Nie ma komentarza „tu nie sprawdziłem uprawnień”. Jest schludny, sformatowany i nazwany zgodnie z konwencją. Przy przeglądzie czyta się go szybciej niż kod kolegi — i właśnie dlatego łatwiej przepuścić w nim błąd.
Co znajdujemy najczęściej
Przy przeglądach kodu pisanego z pomocą asystentów AI powtarza się ten sam zestaw problemów:
- Brak autoryzacji na poziomie zasobu. Endpoint sprawdza, czy użytkownik jest zalogowany, ale nie sprawdza, czy to jego zamówienie. Klasyczne Broken Access Control, numer jeden w OWASP Top 10.
- Walidacja tylko po stronie frontu. Formularz w Reakcie waliduje poprawnie, backend przyjmuje wszystko. Walidacja frontowa jest wygodą dla użytkownika, nigdy zabezpieczeniem.
- Walidacja oderwana od znaczenia pola.
string|max:255na numerze telefonu, polu NIP i imieniu. Technicznie waliduje, biznesowo nie waliduje niczego. - Dane wrażliwe w logach. Wrzucony w ramach debugowania zrzut całego requestu, w którym leci token albo hasło.
- Sekrety w repozytorium. Klucz API wklejony w kod, bo tak było w przykładzie z promptu.
- Zapytania N+1 i brak indeksów. Działa na danych testowych, kładzie się na produkcyjnych.
- Nieobsłużone wyjątki i wycieki szczegółów błędów. Stack trace trafia do odpowiedzi API.
- Biblioteki dobrane „z pamięci” modelu. Paczki porzucone, zarchiwizowane albo z aktywnym CVE — model zna je z czasu treningu, nie z dzisiejszego stanu rejestru.
Ostatni punkt jest wyjątkowo podstępny, bo tu AI potrafi wymyślić nazwę pakietu, który nie istnieje. Jeżeli ktoś zarejestruje taką nazwę w npm czy Packagiście, bezrefleksyjna instalacja z podpowiedzi staje się realnym wektorem ataku na łańcuch dostaw.
Jak audytujemy kod z AI w Nice Code
Audyt prowadzimy w pięciu krokach. Kolejność ma znaczenie — automat idzie pierwszy, bo odsiewa oczywiste rzeczy i zostawia czas człowiekowi na to, czego automat nie zobaczy.
1. Inwentaryzacja i zależności
Zaczynamy od tego, z czego aplikacja jest zbudowana. composer audit i
npm audit pod kątem znanych podatności, plus przegląd ręczny: czy paczka
nie jest oznaczona jako deprecated, czy repozytorium na GitHubie nie jest
zarchiwizowane, kiedy było ostatnie wydanie. Sprawdzamy też, czy wszystkie
zależności w ogóle istnieją w oficjalnych rejestrach i czy pliki lock są
w repozytorium.
2. Skan statyczny i przegląd historii
Analiza statyczna pod kątem OWASP Top 10, skan repozytorium pod kątem sekretów — również w historii commitów, nie tylko w bieżącym stanie, bo usunięcie klucza nowym commitem nie usuwa go z repo. Na tym etapie odpadają też rzeczy czysto formalne: niespójny styl, martwy kod, pozostałe po generowaniu fragmenty „do uzupełnienia”.
3. Przegląd ręczny warstwy dostępu
To jest część, której nie da się zautomatyzować, i to tutaj znajdujemy najpoważniejsze błędy. Przechodzimy endpoint po endpoincie i odpowiadamy na jedno pytanie: kto może to wywołać i na czyich danych. W Laravelu oznacza to sprawdzenie, czy każdy zasób ma Policy albo Gate i czy autoryzacja faktycznie jest wywoływana, a nie tylko zdefiniowana. Równolegle sprawdzamy, czy walidacja siedzi w Form Requestach i czy reguły odpowiadają rzeczywistym danym — że w imieniu nie przejdą cyfry, a w kwocie wartość ujemna.
4. Testy i przypadki brzegowe
Patrzymy, co jest pokryte testami i czego w testach nie ma. Kod generowany przez AI zwykle ma testy ścieżki szczęśliwej, bo taki był prompt. Brakuje testów na odrzucenie niepoprawnych danych (422), na żądanie nieuwierzytelnione (401) i na żądanie uwierzytelnione, ale bez uprawnień (403). Te trzy przypadki dopisujemy zawsze — są najtańszym sposobem, żeby ta sama luka nie wróciła przy następnej zmianie.
5. Raport z priorytetami
Na koniec dostajesz listę znalezisk uszeregowaną według realnego ryzyka, a nie według liczby ostrzeżeń ze skanera. Każdy punkt ma lokalizację w kodzie, opis konsekwencji i propozycję poprawki. Rozdzielamy to, co trzeba naprawić przed wdrożeniem, od tego, co może poczekać na kolejną iterację. Poprawki możemy wprowadzić sami albo zostawić je Twojemu zespołowi — to Twoja decyzja.
Czego nie znajdzie sam skaner
Warto mieć realistyczne oczekiwania wobec narzędzi automatycznych. Skaner
świetnie wyłapuje wzorce: konkatenację SQL-a, wywołanie eval, znaną
podatność w zależności. Nie wyłapuje natomiast błędów, które są poprawnym
kodem realizującym złą regułę biznesową.
Skaner nie wie, że rabat kontrahenta nie powinien być widoczny dla innego kontrahenta. Nie wie, że status zamówienia można zmieniać tylko w jedną stronę. Nie wie, że to pole miało być tylko do odczytu po akceptacji dokumentu. Z punktu widzenia analizy statycznej wszystkie te przypadki to zwyczajny, czysty kod.
Dlatego audyt, który sprowadza się do wklejenia raportu ze skanera, jest wart tyle, co sam skaner. Wartość pojawia się dopiero wtedy, gdy ktoś zestawi kod z tym, jak biznes ma faktycznie działać.
Kiedy warto zrobić audyt
Nie każdy projekt go potrzebuje w tym samym momencie. Sygnały, przy których warto się zatrzymać:
- Aplikacja powstała w dużej części z pomocą asystenta AI i nikt z doświadczeniem w bezpieczeństwie nie przeszedł przez nią całościowo.
- Przetwarzacie dane osobowe, dane kontrahentów albo płatności.
- Projekt pisał zewnętrzny wykonawca i przejmujecie go do utrzymania.
- Zbliża się wdrożenie produkcyjne albo integracja z ERP.
- Coś już się wydarzyło — nietypowy ruch, podejrzane zamówienia, wyciek.
Jeżeli budujesz dopiero pierwszą wersję, taniej jest ustawić zasady na starcie, niż audytować po fakcie. Opisaliśmy to podejście w przewodniku jak stworzyć moduł PrestaShop z pomocą AI — znajdziesz tam gotowy zestaw reguł, który można podłączyć modelowi jeszcze przed napisaniem pierwszej linijki.
FAQ
Czy to znaczy, że nie powinienem używać AI do pisania kodu? Nie. AI realnie przyspiesza pracę i sami z niej korzystamy codziennie. Zmienia się natomiast rozkład czasu: mniej go idzie na pisanie, więcej na weryfikację. Kto oszczędza na tym drugim, oddaje całą uzyskaną przewagę z nawiązką.
Ile trwa audyt? Zależy od wielkości projektu i od tego, czy jest to przegląd całej aplikacji, czy pojedynczego modułu. Zakres i termin ustalamy po wstępnym spojrzeniu na repozytorium — bez tego każda liczba byłaby zgadywaniem.
Czy musicie dostać dostęp do produkcji? Nie. Do audytu kodu wystarczy repozytorium. Dostęp do środowiska testowego pomaga, ale produkcja nie jest potrzebna.
Audytujecie tylko Laravel i PrestaShop? Najmocniejsi jesteśmy w PHP (Laravel, PrestaShop, Magento) i w React. Przy innych stosach mówimy wprost, czy jesteśmy właściwym adresem.
Co jeśli audyt wykaże dużo problemów? To normalny wynik i nie jest to powód do paniki — raport jest po to, żeby uszeregować je według ryzyka. Zwykle kilka pozycji trzeba naprawić od razu, a reszta to dług techniczny do rozłożenia w czasie.
Podsumowanie
Dane Veracode są jednoznaczne w jednym punkcie: skoro bezpieczeństwo kodu generowanego przez modele nie poprawia się wraz z ich rozwojem, to warstwa weryfikacji nie jest etapem przejściowym, tylko stałym elementem procesu. Asystent AI przyspiesza wytwarzanie i jednocześnie przyspiesza wprowadzanie podatności — audyt jest tym, co rozdziela te dwa efekty.
U nas sprowadza się to do prostej zasady: automat odsiewa znane wzorce, człowiek sprawdza, kto ma dostęp do czego, a testy pilnują, żeby raz naprawiony błąd nie wrócił.
Masz aplikację napisaną z pomocą AI i chcesz wiedzieć, co w niej siedzi? Napisz do nas — spojrzymy na repozytorium i powiemy, czy audyt ma sens i w jakim zakresie.
Zobacz też naszą ofertę dedykowanego oprogramowania i modułów PrestaShop. Jeśli potrzebujesz programisty na stałe do zespołu, sprawdź body leasing oraz artykuł o tym, dlaczego jedna osoba potrafi dziś zastąpić mały zespół.