Agno i LMDeploy: dwie krytyczne granice runtime'u agentów AI
CVE-2026-76832 pozwala opuścić base_dir Agno, a CVE-2026-76850 wykorzystuje pickle w LMDeploy. Analizujemy poprawki, ekspozycję i izolację.
- AUTOR
- Karol Rapacz / CEO Breachroad · OSCP · PNPT
- PUBLIKACJA
- 19 sierpnia 2026
- CZAS CZYTANIA
- 18 min czytania
- TEMAT
- Bezpieczeństwo AI
Dwa rekordy bezpieczeństwa analizowane 19 sierpnia 2026 roku pokazują różne sposoby przekroczenia granicy runtime’u AI. CVE-2026-76832 dotyczy path traversal w PythonTools frameworka Agno: nazwa pliku mogła opuścić skonfigurowany base_dir, prowadząc do odczytu, zapisu lub wykonania pliku. CVE-2026-76850 dotyczy LMDeploy: peer connector dla disaggregated serving odbierał obiekty ZeroMQ przez recv_pyobj(), a więc wykonywał pickle.loads() na danych zdalnego peera.
Problemy mają osobne projekty, warunki i chronologię. Agno wprowadziło ochronę w wydaniu 2.3.24 już 8 stycznia; rekord CVE został upubliczniony 19 sierpnia. LMDeploy 0.16.0 ukazał się 19 sierpnia i zastąpił pickle formatem JSON; rekord CVE wskazuje publiczną datę 29 lipca i podatny zakres 0.9.2 do wersji wcześniejszych niż 0.16.0. Wspólną lekcją nie jest więc jedna kampania, lecz zbyt szerokie zaufanie do granicy katalogu i transportu klastra.
CVE-2026-76832: Agno PythonTools opuszcza base_dir
PythonTools udostępnia agentowi działania takie jak odczyt pliku, zapis do pliku i uruchomienie pliku Python. W podatnej implementacji ścieżka była budowana przez połączenie base_dir z kontrolowanym argumentem file_name. Samo joinpath() nie gwarantuje, że wynik pozostaje wewnątrz katalogu bazowego.
Jeżeli file_name zawiera sekwencje katalogu nadrzędnego, ścieżkę absolutną lub odpowiednio przygotowane dowiązanie, rezultat może wskazywać plik poza oczekiwanym workspace. Atakujący nie musi wywoływać narzędzia ręcznie. Instrukcja może znaleźć się w issue, repozytorium, dokumencie RAG lub treści strony analizowanej przez agenta. Model przekazuje argument zgodny ze schematem, ale szkodliwy względem polityki systemu plików.
Skutek działa z uprawnieniami procesu Agno. Odczyt może ujawnić zmienne, klucze, konfigurację chmury i kod innych projektów. Zapis może zmodyfikować skrypt uruchamiany później, plik startowy, konfigurację aplikacji albo artefakt CI. Funkcja run_python_file zwiększa wpływ do wykonania kodu, jeżeli agent wybierze plik poza zaufanym katalogiem.
CVE otrzymał CVSS 3.1 równy 8,8 i CVSS 4.0 równy 8,5 według rekordu CNA. Ocena zakłada interakcję użytkownika lub przetworzenie treści prowadzącej do wywołania narzędzia. Nie oznacza to, że każda aplikacja Agno automatycznie udostępnia PythonTools; trzeba sprawdzić rzeczywistą konfigurację agenta.
Jak działa poprawka Agno
Commit naprawczy wprowadza kontrolę restrict_to_base_dir oraz weryfikację ścieżki. Poprawny wzorzec rozwiązuje ścieżkę do postaci kanonicznej i sprawdza, czy pozostaje ona względna wobec rozwiązanego katalogu bazowego. Wydanie Agno 2.3.24 opisuje automatyczne blokowanie operacji poza kontekstowym base_dir w PythonTools i MLXTranscribeTools.
Flaga może zostać świadomie wyłączona, aby pozwolić na działania poza katalogiem. To oznacza, że sama wersja biblioteki nie zawsze wystarcza jako dowód mitygacji. Zespół musi sprawdzić konfigurację restrict_to_base_dir, niestandardowe subclassy i wrappery, które mogą ominąć nową funkcję.
Nawet prawidłowe resolve() nie jest sandboxem systemu operacyjnego. Proces nadal może czytać wszystko, do czego ma uprawnienia przez inne narzędzie lub bibliotekę. Istnieją też wyścigi między sprawdzeniem a użyciem, mounty i specjalne pliki. Najlepszą obroną jest uruchomienie agenta w osobnym kontenerze lub mikroVM z jednorazowym filesystemem, read-only root, minimalnym workspace i bez sekretów hosta.
CVE-2026-76850: pickle w peer connectorze LMDeploy
LMDeploy służy do wdrażania i serwowania modeli. W trybie disaggregated serving różne silniki komunikują się, aby rozdzielić etapy oraz transferować stan. Podatny handle_zmq_recv odbierał wiadomość przez ZeroMQ recv_pyobj(). Funkcja najpierw deserializuje obiekt Python przy użyciu pickle, a dopiero później kod sprawdzał, czy wynik jest instancją DistServeCacheFreeRequest.
Kontrola typu po deserializacji nie tworzy granicy. Protokół pickle może wskazać funkcję, która zostanie wywołana podczas odtwarzania obiektu. Złośliwy payload wykonuje akcję przed isinstance. Jest to klasyczny przypadek CWE-502: Deserialization of Untrusted Data.
Rekord CVE wyjaśnia także drogę do kontrolowanego peera. Endpointy /distserve/p2p_initialize i /distserve/p2p_connect przyjmują informacje o zdalnym silniku, w tym adres ZeroMQ. Uwierzytelnianie API nie jest wymagane, jeżeli serwer uruchomiono z domyślnym api_keys=None. Atakujący osiągający API może skierować silnik do własnego endpointu ZMQ i dostarczyć obiekt do deserializacji.
CVSS 3.1 wynosi 9,8, a CVSS 4.0 — 9,3. Warunkiem jest włączony tryb disaggregated serving; pętla odbioru nie startuje w zwykłym wdrożeniu bez tej funkcji. Zewnętrzna osiągalność API oraz brak kluczy decydują o praktycznym ryzyku.
LMDeploy 0.16.0 przechodzi na JSON
Poprawka zastępuje pickle protokołem JSON dla żądań P2P ZeroMQ. JSON reprezentuje dane, nie dowolny graf obiektów i funkcji Pythona. To usuwa zdolność wykonania kodu wbudowaną w mechanizm odtwarzania pickle. Nadal potrzebne są schema validation, limity, wersjonowanie protokołu i obsługa nieznanych pól.
Aktualizacja do 0.16.0 lub nowszej wspieranej wersji jest podstawą dla zakresu 0.9.2–<0.16.0 podanego w rekordzie. Przy klastrze wielowęzłowym trzeba zaplanować zgodność protokołu: częściowe wdrożenie może sprawić, że stary peer oczekuje pickle, a nowy wysyła JSON. Dokumentacja wydania i test canary powinny potwierdzić obsługiwany tryb rolling upgrade.
JSON nie uwierzytelnia peera. Należy włączyć klucze API, odizolować endpointy control plane, zastosować mTLS lub uwierzytelniony service mesh oraz ograniczyć ZeroMQ do tożsamości i adresów klastra. Endpoint inicjujący połączenie nie powinien przyjmować dowolnego adresu z sieci użytkowników.
„Zaufana sieć klastra” to wejście niezaufane
W systemach AI ruch między workerami bywa traktowany jako wewnętrzny i bezpieczny. Tymczasem współdzielony Kubernetes, notebook dewelopera, przejęty worker lub błędnie wystawiony API mogą stać się źródłem wiadomości. Jeżeli protokół wykonuje deserializację obiektową bez kryptograficznej tożsamości peera, granica klastra jest tylko założeniem.
Segmentacja powinna rozdzielać endpoint publiczny inference, control plane i transport P2P. NetworkPolicy lub firewall musi ograniczać zarówno połączenia przychodzące, jak i wychodzące z engine. Egress ma znaczenie, bo podatny node aktywnie łączy się z adresem podanym przez żądanie. Lista hostów powinna wynikać z rejestru usług, nie z body użytkownika.
Workload identity pozwala dodatkowo związać silnik z namespace, service account i konkretnym certyfikatem. Krótkotrwałe poświadczenia i wzajemne TLS utrudniają podszycie się pod peer. Log powinien rejestrować inicjatora, wybrany endpoint, wynik polityki i wersję protokołu bez zapisywania tensorów czy treści promptów.
Plan sprawdzenia Agno
Najpierw znajdź wszystkie aplikacje używające PythonTools lub MLXTranscribeTools, również prywatne prototypy i notebooki. Potwierdź wersję z działającego środowiska, nie tylko z lockfile w repozytorium. Następnie sprawdź, czy restrict_to_base_dir pozostaje włączone i jaki katalog jest bazą.
Po aktualizacji do 2.3.24 lub nowszej:
- uruchom testy regresji z nazwami zawierającymi
.., ścieżkami absolutnymi, symlinkami i różnymi separatorami; - ogranicz mount workspace do jednego zadania i ustaw root filesystem jako read-only;
- usuń socket Dockera, token service account, poświadczenia chmurowe i katalog domowy hosta;
- alertuj na odmowy
_check_pathoraz próby dostępu poza workspace; - przejrzyj wcześniejsze wywołania
read_file,save_to_fileirun_python_filepod kątem nietypowych ścieżek; - zrotuj sekret tylko wtedy, gdy mógł zostać odczytany lub brak logów uniemożliwia wykluczenie dostępu w istotnym środowisku.
Nie testuj przez odczytywanie rzeczywistych sekretów. Użyj plików znacznikowych w izolowanej przestrzeni i potwierdź, że polityka odrzuca operację.
Plan sprawdzenia LMDeploy
Zidentyfikuj wersje 0.9.2–<0.16.0, sposób wystawienia API i flagę disaggregated serving. Sprawdź, czy api_keys było None, kto osiąga endpointy /distserve/* i czy engine mógł łączyć się z dowolnym adresem. Aktualizuj cały klaster do 0.16.0 z testem zgodności.
W logach szukaj wywołań inicjalizacji P2P z adresami spoza klastra, nieoczekiwanych połączeń ZeroMQ, restartów engine, nowych procesów potomnych i modyfikacji filesystemu. Zachowaj obrazy kontenerów, audit log API i przepływy sieciowe przed czyszczeniem. Jeżeli istnieje prawdopodobieństwo RCE, rotuj tokeny osiągalne z workloadu i odbuduj node z zaufanego obrazu.
Fakty i wnioski Breachroad
Mechanizm path traversal, metody narzędzi, wyniki CVSS i commit Agno pochodzą z rekordu CVE i repozytorium. Zakres LMDeploy, recv_pyobj(), endpointy, domyślne api_keys=None, warunek disaggregated serving i poprawka JSON pochodzą z CVE oraz wydania 0.16.0. Izolacja mikroVM, mTLS, hunting i rotacja są zaleceniami Breachroad.
Nie twierdzimy, że oba CVE były częścią jednej kampanii albo że każdy agent i serwer LMDeploy jest osiągalny. Wspólny wniosek brzmi: kontrola katalogu nie zastępuje sandboxa, a wewnętrzny transport nie zastępuje uwierzytelnionego protokołu.
Szkolenia z bezpieczeństwa AI i cyberbezpieczeństwa pomagają zespołom rozpoznać te granice w kodzie i architekturze. Audyt bezpieczeństwa AI może zweryfikować narzędzia agentów, filesystem, API serving, protokoły P2P, sieć i tożsamości workloadów.


