Pętla zwrotna nie jest granicą zaufania: CVE-2026-2611
19 maja 2026 roku National Vulnerability Database opublikowała CVE-2026-2611. Funkcja Assistant w MLflow 3.9.0 nieprawidłowo weryfikowała pochodzenie żądań na lokalnych endpointach /ajax-api. Złośliwa strona otwarta w dowolnej karcie mogła wysyłać żądania cross-origin do lokalnego serwera Assistant, zmienić jego konfigurację tak, aby włączyć tryb pełnego dostępu, a następnie wykonywać dowolne polecenia przez dołączoną integrację Claude Code CLI. Huntr ocenił podatność w CVSS 3.0 na 9,6 (CRITICAL), z wektorem AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H: osiągalną z sieci, o niskiej złożoności, niewymagającą uprawnień, ze zmianą zakresu oraz pełnym wpływem na poufność, integralność i dostępność.1
Przyczyna źródłowa nie jest nowa, a naprawa nie jest egzotyczna. Nowy jest model zagrożeń. Każda lokalna usługa, która nasłuchuje na interfejsie pętli zwrotnej i udostępnia karcie przeglądarki endpointy modyfikujące stan, staje się powierzchnią granicy zaufania. Gdy taka usługa steruje agentem AI zdolnym do czytania plików, uruchamiania poleceń powłoki i wypychania kodu, „tylko pętla zwrotna” przestaje działać jako kontrola bezpieczeństwa. CVE-2026-2611 to podręcznikowy przykład całej klasy podatności, które będą pojawiać się regularnie, w miarę jak kolejne narzędzia deweloperskie będą opakowywać agentów LLM w lokalne interfejsy webowe.
W skrócie
CVE-2026-2611 opisuje błąd niepełnej enumeracji ścieżek objętych CORS w MLflow 3.9.0. Funkcja Assistant udostępniała endpointy /ajax-api/, których nie obejmowała kontrola is_api_endpoint() w MLflow, więc middleware blokujące CORS je pomijało. Na dodatek starsze CORSMiddleware dopuszczało allow_origins=["*"], przez co żądanie cross-origin z evil.com mogło wywołać /ajax-api/3.0/mlflow/assistant/config, przełączyć Assistant w tryb pełnego dostępu, a następnie wysyłać polecenia powłoki przez dołączoną integrację Claude Code CLI.1 Poprawka upstream (PR #20832, „Block CORS for ajax paths”) w MLflow 3.10.0 dodaje /ajax-api/ do enumeracji chronionych ścieżek i zawęża allow_origins do skonfigurowanej listy dozwolonych źródeł oraz wyrażenia regularnego dla localhost.2 Poniższe zabezpieczenia dotyczą całej klasy lokalnych agentów AI, nie tylko MLflow.
Najważniejsze wnioski
| Rola | Działanie |
|---|---|
| Inżynierowie AI/ML uruchamiający MLflow | Zaktualizować do MLflow 3.10.0; sprawdzić każdy inny lokalny interfejs agenta AI nasłuchujący na interfejsie pętli zwrotnej |
| Zespoły bezpieczeństwa | Dodać „lokalną usługę agenta LLM na interfejsie pętli zwrotnej” do szablonów modelowania zagrożeń; jedna luka CORS w tej klasie oznacza RCE |
| Autorzy narzędzi budujący lokalne interfejsy agentów | Enumerować każdy prefiks ścieżki API w jednym miejscu; zakazać allow_origins=["*"] nawet na serwerach związanych z localhost; chronić destrukcyjne endpointy świeżym gestem użytkownika, nie samym pochodzeniem żądania |
| Liderzy inżynierii | Izolować agentów kodujących AI zespołu i traktować ich możliwość wykonywania poleceń jako rzeczywisty zasięg szkód |
Jak wygląda łańcuch ataku
Łańcuch ma 5 krótkich kroków. Każdy wykorzystuje autoryzowane możliwości; zawodzi ich połączenie.
| Krok | Działanie | Co kontroluje atakujący |
|---|---|---|
| 1 | Użytkownik uruchamia lokalnie mlflow ui; Assistant nasłuchuje na interfejsie pętli zwrotnej, domyślnie 127.0.0.1 |
Jeszcze nic, to lokalna usługa |
| 2 | Użytkownik otwiera evil.com w dowolnej karcie, gdy MLflow działa |
Treść strony |
| 3 | evil.com wysyła fetch('http://127.0.0.1:5000/ajax-api/3.0/mlflow/assistant/config', { method: 'PUT', credentials: 'include', body: ... }) |
Żądanie cross-origin |
| 4 | MLflow przyjmuje żądanie, bo /ajax-api/ nie było w enumeracji ścieżek blokowanych przez CORS; konfiguracja Assistant przełącza się na pełny dostęp |
Konfigurację środowiska wykonawczego Assistant |
| 5 | evil.com wysyła ładunki z poleceniami przez endpoint czatu Assistant; MLflow wywołuje lokalne Claude Code CLI z pominiętymi kontrolami uprawnień; polecenia działają jako użytkownik |
Dowolne wykonanie kodu |
Atakujący nie musi nic instalować na komputerze ofiary. Wystarczy, że ofiara załaduje jedną kartę z ładunkiem, gdy działa mlflow ui. Atak działa z dowolnego źródła, ponieważ starsze CORSMiddleware miało allow_origins=["*"], a middleware blokujące CORS na poziomie ścieżek nie obejmowało prefiksu /ajax-api/.2
Integracja Claude Code jest wzmacniaczem uprawnień. ClaudeCodeProvider w MLflow lokalizuje lokalne claude CLI przez shutil.which("claude"), a gdy go brakuje, instruuje użytkowników, by zainstalowali je poleceniem npm install -g @anthropic-ai/claude-code. Gdy flaga konfiguracji permissions.full_access w Assistant jest włączona, dostawca rozszerza wywołanie CLI o --permission-mode bypassPermissions, czyli tryb Claude Code, który wyłącza interaktywne prośby o pozwolenie i pozwala wywołaniom narzędzi działać bez potwierdzenia użytkownika.3 Autoryzowane możliwości agenta, czyli powłoka, zapis plików i HTTP, stają się zdalną powłoką z UX opartym na języku naturalnym. Dokładnie tego chce atakujący.
Przyczyną źródłową jest niepełna enumeracja ścieżek
Po przeczytaniu commita z poprawką błąd staje się oczywisty. W security_utils.py MLflow znajdowała się stała:
API_PATH_PREFIX = "/api/"
oraz kontrola:
def is_api_endpoint(path: str) -> bool:
return path.startswith(API_PATH_PREFIX) and path not in TEST_ENDPOINTS
Middleware blokujące CORS wywoływało is_api_endpoint(), aby zdecydować, czy zastosować walidację pochodzenia. Każda ścieżka, która nie zaczynała się od /api/, przechodziła jako ścieżka spoza API. Assistant wprowadził /ajax-api/ dla endpointów używanych przez przeglądarkę. Nikt nie zaktualizował is_api_endpoint(), żeby uwzględnić nowy prefiks.
Poprawka to jednoliniowa zmiana zachowania w security_utils.py:2
API_PATH_PREFIX = "/api/"
AJAX_API_PATH_PREFIX = "/ajax-api/"
def is_api_endpoint(path: str) -> bool:
return (
path.startswith(API_PATH_PREFIX) or path.startswith(AJAX_API_PATH_PREFIX)
) and path not in TEST_ENDPOINTS
Do tego dochodzi równoległa poprawka w fastapi_security.py, która zastępuje allow_origins=["*"] w starszym CORSMiddleware skonfigurowaną listą dozwolonych źródeł oraz wyrażeniem regularnym dla localhost. Łącznie zmiana ma 5 dodanych i 2 usunięte linie w security_utils.py oraz 5 dodanych i 1 usuniętą linię w fastapi_security.py.2
To cała podatność. CVE-2026-2611 powstało dlatego, że wydano nowy prefiks endpointu bez aktualizacji jednej funkcji określającej, które ścieżki otrzymują ochronę CORS. Powierzchnią ataku była zapomniana stała konfiguracyjna.
Dlaczego ten wzorzec się powtórzy
Ten błąd jest starszy niż MLflow i starszy niż agenci AI. Frameworki webowe popełniają błędy enumeracji ścieżek od kiedy istnieje sama enumeracja ścieżek. Zmieniły się konsekwencje. Lokalny interfejs webowy, który zapominał wyliczyć jeden endpoint, kiedyś ujawniał część danych konfiguracyjnych albo wywoływał odmowę usługi. Lokalny interfejs webowy, który zapomina wyliczyć jeden endpoint, gdy uruchamia agenta kodującego AI z dostępem do powłoki, ujawnia całą maszynę dewelopera.
Lokalne interfejsy agentów AI to rosnąca kategoria. W zakres wchodzi każde narzędzie, które opakowuje agenta LLM w interfejs przeglądarkowy serwowany z 127.0.0.1: trackery eksperymentów z panelami czatu, jak w MLflow, wtyczki IDE sterujące agentem przez lokalne API, serwery trybu deweloperskiego, z którymi rozszerzenie komunikuje się przez HTTP, webowe dashboardy dla lokalnych runnerów modeli. Każde nasłuchuje na interfejsie pętli zwrotnej. Każde udostępnia przeglądarce endpointy modyfikujące stan. Każde uruchamia agenta z prawem wywoływania powłoki, zapisywania plików i wypychania do git. Każde z nich dzieli od CVE-2026-2611 dokładnie jeden błąd enumeracji ścieżek.
Wspólne założenie tej klasy brzmi: „polityka same-origin przeglądarki ochroni serwer na interfejsie pętli zwrotnej, bo prawidłowe żądania też przychodzą z 127.0.0.1”. Założenie pada w chwili, gdy jakakolwiek bramka cross-origin, czyli CORS, walidacja nagłówka Host, walidacja nagłówka Origin albo nagłówki metadanych fetch, pomija prefiks endpointu. Przeglądarka nie chroni serwera, jeżeli serwer jawnie przyjmuje żądanie.
Atak cichego wyjścia, który opisałem w marcu, łamie to samo założenie zaufania w przeciwnym kierunku. Ciche wyjście ukrywa wyjście przez autoryzowane narzędzia agenta. Błędna klasyfikacja pętli zwrotnej ukrywa wejście przez autoryzowane wywołania sieciowe przeglądarki. W obu przypadkach sprowadza się to do jednego: autoryzowane możliwości agenta LLM stają się prymitywem ataku zawsze wtedy, gdy zawodzi choć jedna kontrola granicy.
Jak naprawdę wygląda ta klasa podatności
Ta klasa ma 4 składniki. Usługa jest podatna, gdy występują wszystkie 4:
- Lokalny serwer webowy związany z interfejsem pętli zwrotnej albo
0.0.0.0. Wariant tylko z pętlą zwrotną jest częstszy. Usługę localhost przed kartą cross-origin chronią kontrole CORS, Host i Origin w przeglądarce, a nie samo wiązanie sieciowe. - Endpointy modyfikujące stan osiągalne bez gestu użytkownika. Zmiany konfiguracji, wysyłanie poleceń, zapisy plików. Jeżeli
fetch()z dowolnego źródła może zmienić stan, usługa jest osiągalna. - Możliwość, którą ma lokalny użytkownik, ale której nie powinna mieć strona webowa. Odczyt katalogu domowego, uruchamianie poleceń powłoki, wywoływanie chmurowych API ze zapisanymi poświadczeniami, modyfikowanie plików źródłowych.
- Jedna brakująca kontrola granicy. Zapomniany prefiks ścieżki, wildcard
allow_origins, brak walidacji nagłówka Host, brak walidacji Origin, brak egzekwowania Sec-Fetch-Site.
CVE-2026-2611 trafiło we wszystkie 4. Poprawka zamyka składnik czwarty dla prefiksu /ajax-api/ konkretnie w MLflow. Składniki od pierwszego do trzeciego nadal występują w każdym innym lokalnym narzędziu agenta AI. Następne CVE z tej klasy będzie wyglądać identycznie, tylko z inną nazwą pakietu.
Obrona przed klasą lokalnych agentów AI na interfejsie pętli zwrotnej
Standardowa rada CORS, czyli ustawienie allow_origins na konkretną listę i włączanie credentials tylko wtedy, gdy to potrzebne, jest konieczna, ale niewystarczająca. Ta klasa wymaga głębszych kontroli.
Enumerować prefiksy ścieżek w jednym miejscu i napisać test, który zawiedzie, gdy pojawi się nowy niechroniony prefiks. Błąd MLflow trafił do wydania, bo zespół zaktualizował is_api_endpoint() przy wprowadzeniu /api/, a zapomniał zrobić to przy /ajax-api/. Test, który iteruje po każdej zarejestrowanej trasie w aplikacji FastAPI, pyta is_api_endpoint(), czy dana trasa jest chroniona, i zawodzi na każdym nierozpoznanym prefiksie, wykryłby problem na etapie PR. Taka kontrola kosztuje około 20 linii kodu testowego.
Walidować nagłówki Origin i Host, nie tylko CORS. CORS chroni przeglądarki przed odpowiedzią serwera. Nie chroni serwera przed przyjęciem żądania. Lokalne usługi agentów AI powinny odrzucać żądania, których nagłówek Origin nie znajduje się na jawnej liście dozwolonych źródeł i których nagłówek Host nie jest 127.0.0.1 ani localhost; to drugie zapobiega atakom DNS rebinding. Obie kontrole powinny domyślnie odmawiać.
Chronić destrukcyjne endpointy przez Sec-Fetch-Site: same-origin. Przeglądarki wysyłają Sec-Fetch-Site: cross-site przy każdym cross-origin fetch(), również z evil.com do 127.0.0.1. Specyfikacja Fetch Metadata Request Headers definiuje wartości tego nagłówka, a Chrome, Firefox, Safari i Edge wszystkie go wysyłają.4 Middleware odrzucające żądania do endpointów modyfikujących stan, jeśli nie mają Sec-Fetch-Site: same-origin, daje dodatkową warstwę niezależną od pamiętania o każdym prefiksie ścieżki.
Umieścić możliwość wykonywania poleceń przez agenta AI w izolowanym środowisku. Claude Code, Cursor, Aider i Continue domyślnie działają z pełnymi uprawnieniami użytkownika, bo daje to najprostszy UX. Izolacja uprawnień dla każdej przestrzeni roboczej, na przykład bwrap z jawnymi regułami montowania w Linuksie, mechanizm izolacji aplikacji w macOS dla narzędzi dostarczanych jako pakiety .app albo kontener dla każdej przestrzeni roboczej w rozwiązaniach wieloplatformowych, zmniejsza zasięg szkód bez odbierania podstawowego przepływu pracy. To zabezpieczenie dodaje tarcie. O to właśnie chodzi.
Traktować commity agenta jako niezaufane, dopóki nie zostaną sprawdzone. Ochrona gałęzi, wymagane przeglądy, podpisane commity. Model „agent AI z dostępem do commitowania” rozpada się w chwili, gdy atakujący przejmuje lokalne środowisko agenta. Sprzeciw recenzenta, opisany w przeglądzie kodu AI, który potrzebuje sprzeciwu, ma tu takie samo zastosowanie. Jeżeli agent AI i człowiek recenzujący zawsze się zgadzają, człowiek jest dekoracją.
Jak prawdopodobnie będzie wyglądać następne CVE z tej klasy
Ciekawe pytanie nie brzmi, czy pojawi się następna podatność o kształcie CVE-2026-2611. Ciekawe pytanie brzmi, który pakiet opublikuje ją pierwszy. Kandydatów nie brakuje w ekosystemie lokalnych narzędzi AI: trackery eksperymentów opakowujące agenta w UI, wtyczki IDE rozmawiające z lokalnym backendem agenta przez HTTP, serwery trybu deweloperskiego otwierane przez dowolne narzędzie podczas pracy, lokalne API, których rozszerzenia używają do sterowania interfejsami agentów. Żadne z nich nie opublikowało jeszcze CVE w takim kształcie. Kilka już ma ścieżki kodu, które wiążą się z interfejsem pętli zwrotnej i przyjmują żądania cross-origin, traktując przeglądarkę jako granicę zaufania.
Ta klasa jest większa niż CVE-2026-2611, a model zagrożeń się przesunął. Maszyna dewelopera w 2026 roku hostuje agenta AI, który może wywoływać powłokę, zapisywać pliki, wypychać commity i wywoływać chmurowe API. Lokalne interfejsy webowe sterujące tymi agentami dziedziczą uprawnienia agenta przez taką granicę zaufania, jaką egzekwuje ich serwer HTTP. Błędna konfiguracja CORS we frameworku webowym z 2020 roku ujawniała część danych konfiguracyjnych. Błędna konfiguracja CORS w lokalnym interfejsie agenta AI z 2026 roku ujawnia całą maszynę dewelopera oraz możliwość wdrażania na produkcję.
Rozwiązaniem nie jest jedna łatka. Rozwiązaniem jest klasa kontroli: jawna enumeracja ścieżek z testami, wielowarstwowa walidacja nagłówków, izolacja uprawnień i dyscyplina przeglądu commitów autorstwa agentów. Narzędzia, które dostarczają jedno bez pozostałych, dostarczają następcę CVE-2026-2611 z innym prefiksem.
FAQ
Czym jest CVE-2026-2611?
CVE-2026-2611 to identyfikator National Vulnerability Database dla błędu nieprawidłowej walidacji pochodzenia w funkcji Assistant w MLflow 3.9.0. Luka pozwala dowolnej stronie webowej wysyłać żądania cross-origin do lokalnego endpointu /ajax-api Assistant, zmienić jego konfigurację tak, aby włączyć tryb pełnego dostępu, a następnie wykonywać dowolne polecenia przez dołączoną integrację Claude Code CLI. Bazowy wynik CVSS 3.0 to 9,6 (CRITICAL). Naprawiono w MLflow 3.10.0 przez PR #20832, „Block CORS for ajax paths”.12
Jaka była przyczyna źródłowa?
Kontrola is_api_endpoint() w MLflow enumerowała tylko prefiks ścieżki /api/. Gdy funkcja Assistant wprowadziła endpointy /ajax-api/, nikt nie zaktualizował enumeracji. Middleware blokujące CORS przepuszczało więc żądania cross-origin do /ajax-api/ bez żadnej kontroli pochodzenia. Druga słabość pogłębiła błąd: starsze CORSMiddleware dopuszczało allow_origins=["*"], więc nawet nieblokowane ścieżki odsyłały permisywne nagłówki CORS do dowolnego źródła. Poprawka dodaje AJAX_API_PATH_PREFIX = "/ajax-api/" do kontroli ścieżek i zastępuje allow_origins=["*"] skonfigurowaną listą dozwolonych źródeł oraz wyrażeniem regularnym dla localhost.2
Jak sprawdzić, czy dana instalacja MLflow jest podatna?
Proszę uruchomić pip show mlflow | grep Version. Według zakresu CPE w NVD podatne są wersje MLflow >=3.9.0,<3.10.0. MLflow 3.10.0, wydane 20 lutego 2026 roku, zawiera poprawkę. Aktualizację można wykonać poleceniem pip install --upgrade mlflow, po czym należy zrestartować wszystkie działające procesy mlflow ui.
Czy atak działa bez zainstalowanego Claude Code CLI?
Krok modyfikacji konfiguracji działa niezależnie od tego. Krok dowolnego wykonania kodu wymaga obecności Claude Code CLI w PATH ofiary, ponieważ Assistant w MLflow wywołuje je przez shutil.which("claude"). Bez CLI najgorszy przypadek sprowadza się do ujawnienia informacji i dowolnej manipulacji konfiguracją zamiast RCE. Dla użytkowników, którzy skonfigurowali backend Claude Code, czyli udokumentowaną ścieżkę konfiguracji Assistant, praktyczna ekspozycja to RCE.
Jak chronić inne lokalne usługi agentów AI?
Należy sprawdzić 4 składniki: wiązanie z interfejsem pętli zwrotnej albo wildcardem, endpointy modyfikujące stan, możliwości na poziomie agenta oraz dowolną pojedynczą kontrolę granicy, która może mieć luki. Trzeba dodać jawne listy dozwolonych Origin, walidację nagłówka Host względem 127.0.0.1/localhost, aby zablokować DNS rebinding, oraz bramki Sec-Fetch-Site: same-origin na destrukcyjnych endpointach. Możliwość wykonywania poleceń przez agenta należy umieścić w izolowanym środowisku za pomocą bwrap, mechanizmu izolacji aplikacji w macOS albo kontenera dla każdej przestrzeni roboczej. Commity git autorstwa agenta powinny pozostać niezaufane do czasu przeglądu.
-
National Vulnerability Database. „CVE-2026-2611: MLflow Assistant improper origin validation”. Opublikowano 19 maja 2026 roku. Bazowy wynik CVSS 3.0: 9,6 (CRITICAL), wektor
CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H, wystawiony przez Huntr. Poprawka trafiła do MLflow 3.10.0. https://nvd.nist.gov/vuln/detail/CVE-2026-2611. Oryginalne zgłoszenie: https://huntr.com/bounties/8462addd-b464-4a84-b6a2-5529604e6e5a. ↩↩↩ -
MLflow. „Block CORS for ajax paths (#20832)”. Scalono 16 lutego 2026 roku przez Tomu Hirata. Zmienione pliki:
mlflow/server/fastapi_security.py(+5/-1),mlflow/server/security_utils.py(+5/-2) oraz aktualizacje testów. Diff zastępuje jednoprefiksową kontrolęis_api_endpoint()kontrolą dwóch prefiksów i zawężaallow_originsstarszegoCORSMiddlewarez["*"]do skonfigurowanej listy dozwolonych źródeł oraz wyrażenia regularnego dla localhost. https://github.com/mlflow/mlflow/commit/8f9c8a53af90842944101eb8b7d60706822c81bc. ↩↩↩↩↩↩ -
Źródła MLflow 3.10.0.
mlflow/assistant/providers/claude_code.py: wyszukiwanieshutil.which("claude")(linie 268, 280, 355); podpowiedź instalacji"Install it with: npm install -g @anthropic-ai/claude-code"(linia 286); eskalacja trybu uprawnieńcmd.extend(["--permission-mode", "bypassPermissions"])zależna odconfig.permissions.full_access(linie 379-381).mlflow/assistant/config.py: domyślna wartośćPermissionsConfig.full_access: bool = False(linia 15).mlflow/server/assistant/api.py: prefiks routera/ajax-api/3.0/mlflow/assistant(linia 63); czytnikGET /config(linia 261); mutatorPUT /config(linia 276) — endpoint PUT jest celem ataku cross-origin, który przełączafull_access. Zweryfikowano względem sdist MLflow 3.10.0 z PyPI (mlflow-3.10.0.tar.gz). ↩ -
W3C. „Fetch Metadata Request Headers”. Definiuje
Sec-Fetch-Sitei jego wartościcross-site,same-origin,same-site,none. Obsługiwane przez Chrome 76+, Firefox 90+, Safari 16.4+ i Edge 79+. https://www.w3.org/TR/fetch-metadata/ ↩