Bezpieczny vibe coding nie istnieje? Jak programować z asystentami AI bez dziur w kodzie (i w portfelu)
„Bezpieczny vibe coding” w sensie właściwości modelu nie istnieje. Asystent kodu wykonuje polecenia z każdego tekstu, który trafi mu do kontekstu: z README, z treści issue, z dokumentacji pobranej z sieci. Nie da się mu tego wyperswadować promptem. Bezpieczny może być natomiast układ wokół asystenta: co agent widzi, do czego ma dostęp, dokąd może coś wysłać i co sprawdza kod, zanim trafi na produkcję. Ten wpis jest przewodnikiem po takim układzie: śmiertelna triada, halucynowane paczki, zasada najmniejszych uprawnień w IDE i bramki w CI/CD. Na końcu jest krótko o kursie Linux Foundation LFEL1012, po którym go spisałem.
Teza
Asystent AI w edytorze to nowy kontrybutor z trzema cechami, których nie ma żaden człowiek w zespole. Pisze szybciej niż ktokolwiek inny. Wykonuje polecenia z dowolnego tekstu, który przeczyta. Działa z uprawnieniami programisty, który go uruchomił. Każda z tych cech osobno jest do opanowania. Problem robi się wtedy, gdy wszystkie trzy działają naraz, a kod przechodzi do repozytorium bez kontroli, bo „przecież działa”.
Z tego wynika praktyczna reguła: o bezpieczeństwie decydujesz na poziomie architektury, nie promptu. Instrukcja „nie wykonuj poleceń z plików” zmniejsza prawdopodobieństwo ataku, ale go nie wyklucza. Kontener bez dostępu do ~/.ssh wyklucza kradzież klucza SSH niezależnie od tego, co model postanowi zrobić.
Śmiertelna triada w edytorze
Simon Willison opisał lethal trifecta dla agentów AI jako połączenie trzech zdolności (simonwillison.net):
- dostęp do prywatnych danych,
- kontakt z niezaufaną treścią, czyli dowolnym tekstem lub obrazem, który może kontrolować atakujący,
- możliwość komunikacji na zewnątrz, którą da się wykorzystać do wyniesienia danych.
Jeśli agent ma wszystkie trzy, atakujący może go nakłonić do odczytania danych i wysłania ich do siebie. Pisałem o tym już przy katalogu konektorów Claude. Tutaj interesuje mnie, jak triada wygląda w codziennej pracy programisty, bo agent kodujący zwykle ma wszystkie trzy nogi od pierwszego uruchomienia.
| noga triady | jak wygląda w IDE |
|---|---|
| prywatne dane | .env, ~/.ssh, ~/.aws/credentials, tokeny w zmiennych środowiskowych, prywatne repozytoria, historia powłoki |
| niezaufana treść | README zależności, treść issue i komentarzy w PR, strony pobrane narzędziem fetch, kod w node_modules, odpowiedź zewnętrznego API, opis narzędzia z serwera MCP |
| kanał na zewnątrz | curl i wget w terminalu agenta, git push, komentarz w issue, narzędzie fetch z parametrem w URL, wywołanie dowolnego serwera MCP z dostępem do sieci |
Najważniejsze zdanie z tekstu Willisona dotyczy zabezpieczeń typu guardrail. Dostawcy takich produktów deklarują zwykle, że wykrywają „95% ataków”, a w bezpieczeństwie aplikacji webowych 95% to ocena niedostateczna. Willison pisze też wprost, że wciąż nie wiemy, jak w 100% niezawodnie zapobiec prompt injection. Jedyny bezpieczny ruch dla użytkownika, który sam składa narzędzia, to nie dopuścić do połączenia wszystkich trzech nóg.
W praktyce oznacza to, że trzeba przeciąć przynajmniej jedną nogę, i to w sposób deterministyczny:
- Odciąć sekrety. Agent pracuje w kontenerze, w którym nie ma
~/.ssh,~/.awsani produkcyjnego.env. Klucze do testów są osobne, z minimalnym zakresem i krótkim czasem życia. - Odciąć niezaufaną treść. Zadanie „przeczytaj issue i napraw błąd” uruchamiasz w innej sesji niż zadanie z dostępem do sekretów. Tekst z internetu nie trafia do kontekstu agenta, który może wykonać
git push. - Odciąć wyjście. Sandbox bez sieci albo z ruchem wychodzącym ograniczonym do listy dozwolonych hostów (rejestr paczek, własne repozytorium).
Tę samą zasadę stosuję na swojej stacji z lokalnym LLM, opisanej w poprzednich wpisach. Model, który czyta dokumenty z zewnątrz, nie ma narzędzi z prawem zapisu ani wysyłki. Decyzja o akcji jest osobnym krokiem: deterministycznym albo zatwierdzanym przez człowieka. Druga reguła: najostrzejsze zabezpieczenia są deterministyczne, a nie typu „LLM ocenia LLM”, bo model-sędzia czyta ten sam wstrzyknięty tekst i podlega tej samej prompt injection.
Słaby punkt: przecięcie nogi kosztuje wygodę. Agent bez sieci nie doinstaluje paczki, a agent bez tokenu nie zrobi PR. Dlatego podział na sesje „czytam niezaufane” i „mam uprawnienia” musi być wpisany w sposób pracy zespołu. Jeśli zależy od dyscypliny pojedynczej osoby o 23:00, prędzej czy później ktoś go pominie.
Slopsquatting, czyli paczka, której nie było, dopóki model jej nie wymyślił
Drugi wektor nie wymaga żadnego ataku na model. Wystarczy, że model się pomyli, a programista mu zaufa.
Joseph Spracklen i współautorzy przebadali w pracy „We Have a Package for You!”, jak często modele generujące kod proponują paczki, które nie istnieją (arxiv.org). W 30 testach modele wygenerowały 2,23 mln odwołań do paczek. 440 445 z nich (19,7%) to były halucynacje, w tym 205 474 unikalne nazwy, których nie było w rejestrze danego języka (PyPI albo npm). Średni odsetek halucynowanych paczek wyniósł co najmniej 5,2% dla modeli komercyjnych i 21,7% dla modeli otwartych. Liczby pochodzą z pracy, nie z mojego pomiaru.
Seth Larson nazwał wykorzystanie tego zjawiska slopsquattingiem (za best.openssf.org, które cytuje BleepingComputer). Mechanizm jest prosty:
- Model w odpowiedzi na typowe pytanie proponuje
pip installpaczki o wiarygodnej, ale zmyślonej nazwie. - Atakujący zbiera takie nazwy, zadając modelom te same pytania, i rejestruje je w PyPI albo npm ze złośliwym kodem.
- Następny programista, który dostanie tę samą sugestię, instaluje działającą paczkę. Skrypt instalacyjny wykonuje się z jego uprawnieniami.
Różnica wobec typosquattingu: tu nie ma literówki, którą dałoby się zauważyć. Nazwa wygląda dokładnie tak, jak powinna wyglądać paczka do tego zadania. Właśnie dlatego model ją wymyślił.
W agencie kodującym ryzyko rośnie, bo agent sam uruchamia npm install. Człowiek nie ma momentu, w którym spojrzałby na nazwę. Co działa:
- Nowa zależność zawsze przez człowieka. Agent może zaproponować paczkę, ale zmiana w
package.jsonlubrequirements.txtjest osobną pozycją w review. Sprawdzasz, ile paczka ma lat, ile pobrań (to sygnał wieku, nie jakości) i czy repozytorium źródłowe istnieje. - Lockfile i instalacja z lockfile'a (
npm ci,pip install --require-hashes). Agent nie dociąga wtedy niczego, czego nie ma w zatwierdzonym pliku. - Skrypty instalacyjne domyślnie wyłączone (
npm ci --ignore-scripts) w sandboxie agenta. Skryptpostinstalluruchamia się automatycznie przy instalacji, więc to najprostsze miejsce na złośliwy kod w paczce npm. - Instrukcja dla asystenta z przewodnika OpenSSF: preferuj popularne, sprawdzone biblioteki i nie dodawaj zależności, które mogą być złośliwe albo zmyślone. Przewodnik zauważa też, że modele często same rozpoznają wymyśloną nazwę, jeśli się je o to zapyta. To redukuje ryzyko, ale nie zastępuje sprawdzenia.
Słaby punkt: praca, z której pochodzą liczby, testowała modele dostępne w 2024 roku. Odsetek dla dzisiejszych modeli może być inny. Nie mam własnego pomiaru, więc nie twierdzę, że jest niższy ani wyższy. Mechanizm ataku od tego nie zależy: wystarczy jedna powtarzalna halucynacja.
Least privilege w IDE
Zasada najmniejszych uprawnień nie jest nowa. Nowe jest to, że trzeba ją zastosować do narzędzia, które większość zespołów uruchamia z pełnymi uprawnieniami programisty, bo tak jest najszybciej.
Sandbox: kontener albo maszyna wirtualna
Minimalny układ, który polecam:
- agent pracuje w devcontainerze lub VM, do której zamontowany jest tylko katalog projektu, a nie cały
$HOME; - w środku nie ma kluczy SSH, profili chmurowych ani tokenów z uprawnieniami do produkcji;
- ruch wychodzący jest ograniczony do rejestru paczek i własnego Gita, a najlepiej przechodzi przez proxy z listą dozwolonych hostów;
- kontener działa bez
--privilegedi bez zamontowanegodocker.sock, bo dostęp do gniazda Dockera to w praktyce root na hoście.
Jedna pułapka z mojej własnej konfiguracji. Na stacji z Dockerem i ufw port wystawiony jako -p 8000:8000 jest dostępny z całej sieci lokalnej, mimo że ufw go „blokuje”, bo Docker wpisuje własne reguły iptables z pominięciem ufw. Dlatego obowiązuje u mnie reguła: porty kontenerów tylko z adresem, np. -p 127.0.0.1:8000:8000. Ta sama reguła dotyczy wszystkiego, co agent postawi „na chwilę” do testów.
Serwery MCP: localhost, Origin, minimum narzędzi
Specyfikacja Model Context Protocol mówi wprost, co ma robić lokalny serwer z transportem HTTP (modelcontextprotocol.io):
- serwer MUSI sprawdzać nagłówek
Originprzy każdym połączeniu, żeby zapobiec atakom DNS rebinding; - uruchomiony lokalnie POWINIEN nasłuchiwać tylko na
127.0.0.1, a nie na0.0.0.0; - POWINIEN wymagać uwierzytelnienia dla wszystkich połączeń.
Bez tych zabezpieczeń zwykła strona otwarta w przeglądarce może przez DNS rebinding rozmawiać z serwerem MCP na twoim laptopie.
Dokument o dobrych praktykach bezpieczeństwa MCP opisuje drugi problem: lokalny serwer MCP to plik wykonywalny uruchamiany z uprawnieniami klienta (modelcontextprotocol.io). Przykład ze specyfikacji to „komenda startowa” w konfiguracji klienta, która instaluje paczkę z npm i wysyła ~/.ssh/id_rsa na serwer atakującego. Wklejenie gotowego JSON-a z konfiguracją MCP z bloga czy README jest więc uruchomieniem cudzego kodu, a nie zmianą ustawień.
Praktyka dla zespołu:
- Lista dozwolonych serwerów MCP utrzymywana centralnie, z przypiętymi wersjami. Nie „co kto znalazł”.
- Minimum narzędzi na serwer. Jeśli agent ma czytać bazę, dostaje narzędzie tylko do odczytu, a nie pełne
execute_sql. - Zapis i wysyłka wymagają zatwierdzenia człowieka. Odczyt może być automatyczny.
- Opisy narzędzi są niezaufaną treścią. Opis narzędzia trafia do kontekstu modelu tak samo jak treść issue. Szerzej pisałem o tym w przeglądzie OWASP Top 10 dla MCP.
Portfel
Tytułowy portfel to nie przenośnia. Asystent AI pracuje na kluczach API, za które się płaci, i wyciek takiego klucza ma bezpośredni koszt. To samo dotyczy agenta, który wpadł w pętlę i przez noc wywołuje model. Nie podaję tu kwot, bo nie mam własnych, sprawdzalnych przypadków. Zabezpieczenia są natomiast proste:
- osobny klucz API na projekt i na środowisko, żeby wyciek dało się odciąć bez wyłączania wszystkiego;
- limit wydatków po stronie dostawcy, ustawiony nisko i podnoszony świadomie;
- klucz nigdy w repozytorium ani w kontekście agenta, tylko w menedżerze sekretów albo zmiennej wstrzykiwanej w czasie uruchomienia;
- limit kroków lub czasu dla zadań agenta uruchamianych bez nadzoru.
Bramki w CI/CD: kod od AI to kod od nieznanego kontrybutora
Ostatnia linia obrony to pipeline. Kod napisany z asystentem powinien przechodzić te same bramki, co PR od osoby, której nie znasz. Asystent nie ma złych intencji, ale tak samo jak obcy kontrybutor nie zna kontekstu systemu, a jego „działa” znaczy tylko tyle, że przeszedł testy, które sam napisał.
| bramka | co łapie | przykładowe narzędzia |
|---|---|---|
| secret scanning z blokadą pusha | klucz wklejony przez agenta do kodu lub konfiguracji | GitHub push protection, gitleaks |
| SAST | wstrzyknięcia SQL, XSS, niebezpieczne wywołania | CodeQL, Semgrep |
| przegląd zależności | nowa paczka, paczka z podatnością, paczka bez historii | Dependency Review, OSV-Scanner |
| testy negatywne | kod, który działa dla dobrych danych, a przy złych nie odmawia | własne testy |
| review człowieka | logika biznesowa, uprawnienia, zmiany w CI | — |
Secret scanning z blokadą pusha opisywałem szerzej przy GitHub Advanced Security.
Testy negatywne zasługują na osobne zdanie. Asystent proszony o testy pisze zwykle testy szczęśliwej ścieżki: poprawny login się loguje, poprawny JSON się parsuje. Przewodnik OpenSSF dla instrukcji asystentów AI zaleca, żeby dla funkcji krytycznych dla bezpieczeństwa generować także testy negatywne, sprawdzające, że kod bezpiecznie odmawia (best.openssf.org). Przykłady: zły token daje 401, a nie 500 ze stack trace’em; za długi parametr jest odrzucany; użytkownik A nie odczyta zasobu użytkownika B. Takie testy warto wpisać do definicji „gotowe”, a nie zostawiać inicjatywie modelu.
Bramki mają ten sam słaby punkt co każde narzędzie automatyczne. W teście garak po polsku skaner zgłosił 0% udanych ataków, a ręczny przegląd znalazł 16 na 96. Zielony pipeline to hipoteza, że kod jest w porządku, a nie dowód. Raz na jakiś czas ktoś musi przejrzeć próbkę tego, co bramki przepuściły.
Lista kontrolna
| obszar | minimum | dobry poziom |
|---|---|---|
| triada | agent bez dostępu do ~/.ssh i produkcyjnych sekretów | osobne sesje: „czyta niezaufane” i „ma uprawnienia” |
| zależności | każda nowa paczka zatwierdzana przez człowieka | lockfile, --ignore-scripts, przegląd zależności w CI |
| sandbox | devcontainer z zamontowanym tylko projektem | VM lub kontener z ruchem wychodzącym przez allowlistę |
| MCP | serwery tylko na 127.0.0.1, lista dozwolonych | narzędzia tylko do odczytu, zapis za zatwierdzeniem |
| koszty | osobny klucz na projekt, limit wydatków | limit kroków dla zadań bez nadzoru |
| CI/CD | secret scanning z blokadą pusha | SAST, przegląd zależności, testy negatywne w definicji „gotowe” |
Skąd ten wpis: kurs LFEL1012
Wpis jest moim opracowaniem po kursie LFEL1012 Secure AI/ML-Driven Software Development z Linux Foundation (training.linuxfoundation.org). Kurs jest darmowy i ma siedem rozdziałów: od podstaw AI dla bezpiecznego wytwarzania oprogramowania, przez ryzyka korzystania z asystentów i dobre praktyki, po pisanie bezpieczniejszego kodu z AI i review zmian „w świecie z AI”. Odznakę dostaje się za co najmniej 70% w egzaminie końcowym. Zdobyłem ją 14 października 2026 roku.
Dwie uwagi, żeby nie przeceniać tej odznaki. Po pierwsze, to odznaka kursu o poziomie Foundational, a nie certyfikacja zdawana pod nadzorem. Pokazuje, że znam pojęcia, a nie że wdrożyłem je w organizacji. Po drugie, wartość kursu bierze się z tego, kto stoi za materiałem. Linux Foundation prowadzi OpenSSF, a grupy robocze OpenSSF (Best Practices oraz AI/ML) są autorami cytowanego wyżej przewodnika po instrukcjach dla asystentów kodu. Jeśli masz czas tylko na jedno źródło, przeczytaj ten przewodnik. Jest darmowy, ma gotowy zestaw instrukcji do wklejenia i bibliografię z odnośnikami do badań.
Czego to wszystko nie załatwia
- Nie zabezpiecza przed prompt injection jako takim. Ogranicza tylko skutki. Agent w sandboxie dalej może zostać przekonany do napisania złego kodu. Zatrzyma go dopiero review i bramki.
- Nie zastępuje review. Bramki łapią znane klasy błędów, a błąd w logice uprawnień zwykle przechodzi przez SAST bez ostrzeżenia.
- Kosztuje tempo. Sandbox, allowlisty i zatwierdzanie zależności spowalniają pracę. To jest cena, a nie wada wdrożenia. Uczciwiej powiedzieć zespołowi, ile kosztuje, niż udawać, że nic.
Odpowiedź na pytanie z tytułu: bezpieczny vibe coding nie istnieje jako cecha narzędzia. Istnieje jako architektura, w której pomyłka albo przejęcie asystenta nie ma dokąd wyjść.