Konfiguracja agenta to nie tylko konfiguracja

Zależności audytuję. Lockfile, advisories, cały rytuał. Dopiero po incydencie z keyv dotarło do mnie, że nie mam podobnego odruchu wobec plików, które mówią agentowi, co może robić w moim repozytorium.

Aug 10, 2026~7 min czytania
Konfiguracja agenta to nie tylko konfiguracja

Konfiguracja agenta to nie tylko konfiguracja

Co robak w keyv zmienił w tym, jak przeglądam pull requesty

Wróciłem po dwóch tygodniach urlopu, zrobiłem git pull w kilku repozytoriach i zabrałem się do pracy. Tyle. Zwykły pierwszy poranek po przerwie.

Potem ktoś podesłał analizę Datadog Security Labs dotyczącą robaka npm, który uderzył w keyv i kilka powiązanych paczek. Przez następną godzinę nie zrobiłem nic z tego, co miałem zaplanowane.

Nie przez zależności. Te sprawdzam. Lockfile, advisories, cały rytuał. Ten odruch mam wyrobiony od lat.

Zatrzymał mnie jeden szczegół schowany gdzieś pod koniec artykułu.

Co właściwie się wydarzyło

4 sierpnia 2026 ktoś przejął konto GitHub maintainera stojącego za keyv i grupą powiązanych bibliotek cache'ujących. Atakujący wypchnął commity bezpośrednio na main i opublikował nowe wersje paczek.

W ciągu kilku minut ten sam payload znalazł się w kilku bibliotekach tego samego autora. Potem zaczął rozprzestrzeniać się dalej — wykorzystywał wykradzione tokeny npm do publikowania zainfekowanych wersji kolejnych paczek.

Sam mechanizm nie był szczególnie wyszukany. W package.json pojawiał się wpis preinstall, który wskazywał na niewielki loader. Uruchamiasz npm install, a loader startuje, zanim Twój własny kod w ogóle dostanie szansę się wykonać.

Potem pobiera większy, drugi etap payloadu, szuka tokenów i innych danych dostępowych w typowych miejscach, a to, co znajdzie, wykorzystuje do dalszego rozprzestrzeniania infekcji.

Skala incydentu zależy od tego, czyje dane weźmiemy pod uwagę — i to samo w sobie sporo mówi o charakterze takich ataków. Różne firmy zajmujące się bezpieczeństwem podawały liczby od kilkuset paczek do ponad dwóch tysięcy zainfekowanych wersji. Nie powstała jedna lista, co do której wszyscy byliby zgodni.

Jedno nie budzi większych wątpliwości: większość osób, które znalazły się w zasięgu ataku, nigdy świadomie nie instalowała tych bibliotek.

Typowy łańcuch prowadził przez eslint, dalej do file-entry-cache, potem flat-cache i dopiero na końcu keyv.

Cztery poziomy niżej.

Takiej zależności nie wybierasz. Po prostu ją dziedziczysz.

Ale nie to mnie zatrzymało. To nadal był klasyczny atak na software supply chain. Groźny, ale dobrze znany. Mamy narzędzia i procedury, które przynajmniej próbują takie rzeczy wykrywać.

Mnie zatrzymało coś innego.

Atakujący dodawał do repozytoriów również pliki konfiguracyjne hooków edytorów i agentów. Konfigurację dla VS Code. Konfigurację dla Claude Code. Zwykłe pliki leżące w repo razem z całą resztą kodu.

W co najmniej jednym przypadku samo otwarcie pobranego repozytorium mogło wystarczyć do uruchomienia payloadu.

Bez npm install.

Bez instalowania czegokolwiek.

Dlaczego tak łatwo to przeoczyć

Nie chcę robić z tego historii o tym, że ktoś był nieuważny. Problem jest ciekawszy.

Te pliki wpadają dokładnie pomiędzy dwa procesy, które osobno działają całkiem dobrze.

Pierwszy to code review.

Człowiek otwiera diff i sprawdza, czy zmiana ma sens. Czyta kod aplikacji, testy, migracje, konfigurację infrastruktury.

Pliki agentów nie wyglądają jak kod aplikacji, więc łatwo je potraktować jako coś pobocznego. Rzut oka, scroll i dalej — trochę jak z diffem lockfile'a, którego większość z nas również nie analizuje linia po linii.

Drugi proces to skanowanie zależności.

Dependabot, Snyk, narzędzia w CI — wszystko, co sprawdza rzeczy zadeklarowane w manifestach i porównuje wersje bibliotek z bazami podatności.

Tyle że konfiguracja agenta nie jest zależnością. Nie ma jej w package.json. Żaden dependency scanner nie ma powodu się nią zainteresować.

W rezultacie powstaje dziwna kategoria plików: są wersjonowane, trafiają do każdego checkoutu, mogą wpływać na zachowanie narzędzi pracujących na repozytorium, a mimo to nie pasują do żadnego z naszych wyrobionych odruchów review.

Nie dlatego, że ktoś świadomie uznał je za bezpieczne.

Po prostu nowe pliki pojawiły się szybciej niż procesy wokół nich.

Dwa poziomy, które warto rozdzielić

Jest tu jedno rozróżnienie, które moim zdaniem ma duże znaczenie dla tego, jak podejść do problemu.

Pierwszy poziom to pliki, które bezpośrednio uruchamiają polecenia.

Definicje hooków. Konfiguracje serwerów MCP. tasks.json w VS Code.

W takich plikach znajduje się komenda, a jakiś element toolchainu ją wykonuje — czasem automatycznie albo przy bardzo niewielkim udziale użytkownika.

To właśnie ten poziom wykorzystano w incydencie z keyv. Łatwo zrozumieć, dlaczego jest niebezpieczny: gdzieś w pliku stoi polecenie, a później to polecenie zostaje uruchomione.

Drugi poziom jest mniej oczywisty.

To pliki, które niczego same nie wykonują, ale mówią agentowi, co ma robić.

CLAUDE.md, slash commands, definicje agentów, skille.

To zwykły markdown.

Sam z siebie nie zrobi absolutnie nic.

Tyle że czyta go agent, który może mieć dostęp do terminala, systemu plików, sieci, repozytorium, a czasem również do credentials.

I właśnie ten drugi poziom wydaje mi się ciekawszy.

Markdown wygląda jak dokumentacja. Nie daje żadnego wizualnego sygnału, że warto zatrzymać się na nim dłużej.

W przypadku skryptu shellowego można rzucić okiem na kilka linii i mniej więcej zobaczyć, czego się spodziewać.

W przypadku instrukcji dla agenta nie jest to takie proste. Efektem pliku nie jest konkretna komenda zapisana w tekście. Efektem jest zachowanie modelu, który tę instrukcję przeczyta i zinterpretuje w kontekście swoich uprawnień.

Co konkretnie sprawdziłem u siebie

Zanim zacząłem to pisać, przejrzałem własny setup. Trochę zakładałem, że znajdę coś, czego wolałbym nie znaleźć.

bash
# co faktycznie jest w repo
git ls-files | grep -E '^\.(claude|vscode|cursor)/'

# czy w tych katalogach są hooki, MCP albo taski
git ls-files -z | grep -zE '^\.(claude|vscode|cursor)/' \
  | xargs -0 grep -lE '"hooks"|"mcpServers"|"tasks"|preLaunchTask' 2>/dev/null

# wywołania powłoki w plikach komend i agentów
grep -rn '^!' .claude/commands/ .claude/agents/ 2>/dev/null

Trzy pliki.

Agent do eksplorowania kodu, komenda /do i skill opisujący workflow.

Wszystko w markdownie. Żadnych hooków. Żadnych serwerów MCP. Żadnych wywołań powłoki.

Czyściej, niż się spodziewałem.

Powinno mnie to uspokoić.

Nie uspokoiło.

Bo moje repozytorium jest bezpieczne pod tym względem dzisiaj. Ma też jednego kontrybutora: mnie.

Wystarczy jeden pull request od jednej dodatkowej osoby. Jeden nowy plik, który przelecę wzrokiem, bo wygląda niegroźnie.

I stan, który właśnie sprawdziłem, przestaje być aktualny.

Co zmieniłem u siebie

Pięć rzeczy, mniej więcej od tych, które dają mi najwięcej za najmniejszy koszt.

Jawny ownership dla katalogów agentów. Dodałem wpisy w CODEOWNERS dla .claude/, .vscode/ i .cursor/. Pull request dotykający któregoś z tych katalogów ma dostać osobne, świadome review, zamiast przejść tylko dlatego, że cała pozostała część zmiany wygląda sensownie.

To najtańsza rzecz na tej liście i prawdopodobnie najważniejsza. Nie rozwiązuje problemu technicznie. Sprawia za to, że wcześniej niewidoczna kategoria plików zaczyna być traktowana jako coś, co wymaga uwagi.

Skrypty lifecycle wyłączone domyślnie w CI. Używam npm ci --ignore-scripts, a wyjątki dopuszczam tylko tam, gdzie paczka rzeczywiście potrzebuje własnego kroku instalacyjnego.

Lista wyjątków ma być krótka i świadoma.

Gdyby payload z keyv trafił na maszynę buildową działającą w takim trybie, preinstall po prostu by się nie wykonał.

Opóźnienie przed przyjęciem świeżo wydanej wersji. Nie chodzi o samo pinowanie zależności. Pin niczego nie daje, jeśli przypniesz akurat zainfekowaną wersję.

Chodzi o cooldown.

Jeśli paczka została opublikowana dziś rano, nie musi automatycznie znaleźć się w moim buildzie tego samego popołudnia. W wielu podobnych kampaniach pierwsze godziny są najbardziej niebezpieczne, bo ekosystem dopiero zaczyna wykrywać, że coś jest nie tak.

Kilka godzin opóźnienia potrafi być zabezpieczeniem samym w sobie.

Lokalna konfiguracja zostaje lokalna. Wszystko, co zawiera ścieżki zależne od konkretnej maszyny, tokeny albo inne dane lokalnego środowiska, trafia do .gitignore i nie powinno pojawiać się w repozytorium.

Przy okazji warto powiedzieć to wprost: .claude.json w katalogu domowym zawiera historię i metadane projektów. To nie jest plik, który warto bez zastanowienia wrzucać na screenshot albo do zgłoszenia supportowego.

I na koniec najbardziej uczciwy punkt. Nie uruchamiam obecnie żadnych serwerów MCP.

To usuwa z mojego środowiska całą klasę potencjalnych problemów.

Tyle że nie był to świadomy wybór bezpieczeństwa. Po prostu tak wygląda dziś mój setup.

Nie zamierzam przedstawiać przypadku jako strategii. Jeśli za miesiąc dodam pierwszy serwer MCP, razem z funkcjonalnością dodam też nową powierzchnię ataku.

To zostanie z nami dłużej niż ten incydent

Tak naprawdę nie jest to historia o npm.

Nie jest też specjalnie historią o Claude Code.

Podstaw inny registry i innego agenta, a problem pozostaje ten sam.

Do naszych repozytoriów doszła nowa kategoria plików. W ciągu mniej więcej półtora roku stała się czymś zupełnie normalnym.

Nasze nawyki review praktycznie się przy tym nie zmieniły.

Odruch ostrożności wobec zależności budowaliśmy przez lata. Lockfile'y, wersje, advisories, dependency scanners, ograniczanie lifecycle scripts — większość z tych praktyk powstała dlatego, że wcześniej ktoś się na czymś sparzył.

W przypadku plików sterujących agentami takiego odruchu jeszcze nie mamy.

A przecież są to pliki, które wpływają na narzędzie mające dostęp do kodu, terminala i często reszty naszego środowiska developerskiego.

Możemy poczekać, aż wystarczająco dużo osób się na tym sparzy i dopiero wtedy zbudować wokół tego dobre praktyki.

Ja wolę zacząć wcześniej.


Analiza Datadog Security Labs dotycząca robaka jest tutaj. Jeśli interesuje Cię techniczny rozbiór payloadu, warto przeczytać ją w całości.

Czy ten wpis był pomocny?

Konfiguracja agentów jako nowa powierzchnia ataku. Wnioski z robaka npm | Code Nomad