Agenty AI napisały kod. Agenty AI zrobiły audyt. Co znalazły?
· · Nie jednym promptem · odcinek 5.5 · Read in English
Kod Zakupomatu w 100% napisały agenty AI. Backend w Pythonie, aplikacja mobilna w Kotlin Multiplatform, wersja webowa, serwer MCP dla Claude i ChatGPT. Ja decydowałem, co powstaje, czytałem zmiany i pilnowałem testów. Ale nie będę udawał, że przeczytałem każdą linijkę pod kątem bezpieczeństwa.
No to kto ma sprawdzić kod pisany przez agenty? Też agenty! 😉 Tylko na pewno nie jednym promptem "znajdź dziury w moim kodzie". Taki prompt da Ci 50 alarmów, z czego 45 to "brak nagłówka X" i "rozważ rate limiting", a dwa prawdziwe problemy giną gdzieś w środku.
Okazja przyszła sama. Na serwerze trzeba było załatać PHP dla zupełnie innego serwisu, ruszyła przeprowadzka wszystkiego na nowy serwer (o niej będzie osobny, duży artykuł), więc skoro i tak sprzątamy - sprawdźmy też własny kod.
Skill, nie prompt
Użyłem security-audit od Cloudflare - skilla open source dla agentów (u mnie Claude Code). Zanim agent go zainstalował, przeczytał jego kod. Skill to instrukcje, które agent wykona z Twoimi uprawnieniami na Twoim komputerze, więc traktuję go jak każdą zależność z internetu.
Skill prowadzi audyt w 6 fazach:
- Rekonesans - mapa systemu: co jest publiczne, gdzie są dane, gdzie granice zaufania.
- Polowanie według "księgi pokrycia" - system jest dzielony na jednostki (u mnie 33), a każdą przeczesują osobne agenty.
- Niezależna walidacja każdego kandydata na podatność przez świeżego agenta, który nie widział, jak trop powstał.
- Ustrukturyzowane wyniki - JSON plus walidator, a nie wolny tekst.
- Druga, niezależna weryfikacja każdego rekordu.
- Raport.
Najważniejsza reguła całego procesu - każdy trop musi pokazać, jaką granicę zaufania przekracza. Kto może co zrobić i czego nie powinien móc. Jeśli tego nie pokaże, to jest "hardening", czyli dobra praktyka do rozważenia, a nie podatność. Ta jedna reguła robi większą różnicę niż cała reszta.
Liczby
- Ok. 60 wywołań agentów: 4 do rekonesansu, 35 "hunterów" w 4 falach, 5 krytyków pokrycia, 9 weryfikatorów kandydatów i 7 weryfikatorów rekordów.
- 33 jednostki pokrycia. Krytycy pokrycia dołożyli 13 jednostek ponad pierwszy plan i odesłali dwie "zamknięte" do ponownego przejrzenia (serwer MCP i aplikację mobilną). Ostatni krytyk powiedział "stop" - brak luk.
- Wynik: 0 potwierdzonych podatności, 6 tropów do walidacji, 3 tropy odrzucone przez weryfikatorów.
Odrzucone tropy są ciekawsze, niż się wydaje. "Brak limitu rozmiaru żądań" odpadł jako ogólny hardening bez pokazanej szkody. "Usunięte konto przeżywa w kopiach zapasowych" odpadł, bo retencja kopii to świadoma, zapisana decyzja w brainie projektu, a polityka prywatności o kopiach mówi. Agent-weryfikator znalazł tę decyzję i przyjął ją do wiadomości. Tak działa pamięć projektu!
Był też spór. Przy jednym tropie drugi weryfikator chciał go odrzucić, dwóch niezależnych utrzymało go jako "do walidacji". Zgodnie z regułą skilla rozstrzygał trzeci, świeży agent. Trochę jak dobra rada architektów, tylko nikt się nie obraża 😂
Rdzeń wyszedł czysto: izolacja danych między rodzinami, klucze, OAuth dla asystentów AI, escapowanie, brak sekretów w repo i w historii gita. Agenty tego szukały i nie znalazły przekroczenia granicy.
Uczciwie: audyt bez uruchamiania kodu
Skill do uruchamiania kodu wymaga sandboxa systemowego. Na tym Macu go nie było, więc agent świadomie niczego nie uruchamiał - cały audyt to czytanie kodu źródłowego. Wszystko, czego nie dało się ustalić z samego kodu, trafiło do kategorii "do walidacji", zamiast udawać pewność. Dlatego "0 potwierdzonych" nie znaczy "0 dziur". Znaczy "0 dziur, które da się udowodnić z kodu". A jeden trop z kategorii "do walidacji" potwierdził się kilka godzin później...
Błąd 1: komentarz, który obiecywał bezpieczeństwo
Limity prób (logowanie, rejestracja, rejestracja klientów OAuth, zgoda OAuth) liczyły się po adresie IP klienta. Adres brała funkcja, która ufała pierwszemu wpisowi w nagłówku X-Forwarded-For. Nad nią stał komentarz w stylu "serwer pośredniczący ustawia ten nagłówek, więc pierwszy wpis jest bezpieczny".
Tylko że Apache przed backendem tego nagłówka nie ustawia. On dopisuje prawdziwy adres na końcu tego, co przysłał klient. Czyli pierwszy wpis wybierał klient. Wystarczyło w każdym żądaniu wysłać inny "adres", żeby za każdym razem dostać nowy budżet prób. Limity działały tylko na tych, którzy grzecznie nic nie zmieniali.
Komentarz brzmiał rozsądnie i miesiącami przechodził przez przeglądy - ludzkie i agentowe. Bo opisywał intencję, a nie zachowanie. Nikt nie sprawdził, czy proxy naprawdę robi to, co obiecuje komentarz.
Poprawka: nagłówkowi ufamy tylko wtedy, gdy przychodzi od lokalnego proxy, i bierzemy ostatni wpis - ten, który dopisał nasz serwer. Do tego test, który wysyła sfałszowany nagłówek i sprawdza, że nie daje nowego budżetu. Na starym kodzie nowe testy padają 3 z 4, czyli naprawdę łapią problem.
Z tego powstała lekcja L-014 w brainie Zakupomatu. Najważniejsze zdanie:
Założenie o infrastrukturze, od którego zależy bezpieczeństwo, musi być wymuszone w konfiguracji albo w kodzie - komentarz go nie wymusza.
I równie ważne - czego ta lekcja nie uczy. Nie "nie ufaj komentarzom". Uczy: gdy komentarz mówi, że coś jest bezpieczne dzięki czemuś poza tym plikiem, to coś trzeba sprawdzić i przypiąć testem albo konfiguracją w repo.
Błąd 2: przypięta wersja, która zamroziła podatność
Backend stoi na FastAPI, a formularze parsuje biblioteka python-multipart. Agent przypiął ją w wersji 0.0.6 na samym początku projektu, dla stabilności wdrożeń. I nikt jej nie ruszał.
Ta wersja parsuje nagłówek Content-Type regexem z wykładniczym backtrackingiem - klasyczny ReDoS, znany jako CVE-2024-24762 i załatany już w wersji 0.0.7. Audyt miał to tylko jako hipotezę, bo przecież niczego nie uruchamiał.
Przy łataniu agent zmierzył to lokalnie. 25 znaków w nagłówku - 7 ms. 50 znaków... ponad minuta! BUM! Przez publiczny endpoint, który nie wymaga logowania, 32 znaki dawały 0,18 s na starej wersji i 0,00 s po aktualizacji. A ponieważ backend jest asynchroniczny, regex mieli w pętli zdarzeń - jedno spreparowane żądanie blokuje całe API, nie tylko swój wątek.
Poprawka: podbicie fastapi do 0.115.14, starlette do 0.46.2 i python-multipart do 0.0.20. Dlaczego nie do najnowszych? Bo stary serwer miał Pythona 3.9, a to najwyższe wersje bez podbijania pydantic, które działają i na 3.9, i na 3.12 z nowego serwera. Pełne podbicie przyjdzie po przeprowadzce.
Lekcja: przypięta wersja to decyzja z datą ważności. Pin chroni przed niespodzianką przy wdrożeniu, ale bez okresowego przeglądu zależności (pip-audit, Dependabot) zamraża znane podatności. Taki przegląd jest teraz w kolejce.
Bonus: przekierowanie, którego nikt nie sprawdzał
Trzecia łatka z tego samego PR-a, dla tych, którzy robią OAuth dla asystentów AI. Serwer pobiera dokument metadanych klienta (CIMD) spod adresu, który podaje klient, i sprawdza ten adres z listą dozwolonych hostów. Tylko że pobieranie podążało za przekierowaniami, a lista sprawdzała pierwszy adres. Teraz przekierowanie kończy się błędem pobrania i tyle.
Co zostało
Trzy tropy i zbiorczy hardening czekają w kolejce. Nie opisuję ich, dopóki nie są załatane - to chyba oczywiste 😉 Wszystkie zgłoszenia trafiły do backlogu ze zwykłą etykietą inbox i przejdą normalny refinement z kryteriami akceptacji. Agent nie przeskoczył procesu tylko dlatego, że "to bezpieczeństwo".
Ile to kosztowało
Audyt trwał mniej niż godzinę... i zjadł cały mój 5-godzinny limit użycia 😮 Zgłoszenia agent dokończył na resztce, a łatanie poszło dopiero po odnowieniu limitu. Dobrze, że jest Mac mini, na którym takie rzeczy dzieją się teraz w tle przez cały dzień. Od decyzji "łatamy" do produkcji minął jeden dzień: 173 testy zielone, CI na dwóch bazach danych zielone.
Kto co zrobił
Agenty: przeczytały i zainstalowały skill, przeprowadziły cały proces, napisały 7 zgłoszeń, łatki, testy, lekcję w brainie i PR.
Ja: zleciłem audyt i jego skalę, oceniłem zgłoszenia, zdecydowałem, co łatamy i w jakiej kolejności. Poprawiłem też plan agenta - chciał sprawdzać zachowanie proxy na starym serwerze, a ja uznałem, że to ma sens dopiero na nowym, skoro przeprowadzka trwa. I dałem zgodę na scalenie i wdrożenie.
Co to znaczy dla Twojego zespołu
Kod pisany przez AI da się audytować przez AI - ale wartość daje proces z niezależną weryfikacją, a nie "wykrywanie". Szukajcie w swoim kodzie komentarzy, które obiecują bezpieczeństwo dzięki czemuś poza plikiem, i przypinajcie je testem. A przypięte wersje traktujcie jak decyzje z datą ważności, nie jak "załatwione".
W ANSLAN pomagamy zespołom wdrażać agentów AI tak, żeby było szybciej, ale i bezpiecznie - z procesem, testami i człowiekiem, który decyduje.
Zakupomat jest darmowy i bez reklam - wspólna lista zakupów dla całej rodziny.