SSD Nodes Learn 🎉 VPS od $5.50/mies.
Przewodniki Matt ConnorAutor: Matt Connor

Prompt injection w agentach programistycznych: analiza

Analiza podatności agentów AI na ataki typu prompt injection. Zidentyfikowano wektory ataku na serwery oraz oceniono skuteczność zabezpieczeń w ograniczaniu ryzyka przejęcia sesji.

Czym jest wstrzykiwanie promptu (prompt injection) w kontekście agenta programistycznego

Wstrzykiwanie promptu w odniesieniu do agenta programistycznego można opisać prosto: tekst odczytywany przez agenta jest traktowany jako instrukcja, którą agent wykonuje. Agent otwiera plik, komentarz do pull requesta, stronę internetową lub wynik wywołania narzędzia. Wszystko to dociera do niego w tej samej formie, co Twoje własne zapytanie. Jeśli atakujący kontroluje jakikolwiek fragment tego tekstu, oznacza to, że pisze on wewnątrz Twojej sesji.

Każdy obecny produkt typu agent posiada tę właściwość. Model otrzymuje jeden ciąg tokenów. Twoje zapytanie, prompt systemowy, zawartość plików oraz wyniki narzędzi są łączone w całość, a model przewiduje, co powinno nastąpić dalej. W tokenach nie istnieje żaden bit uprawnień. W formacie danych nie ma informacji o tym, którą część autoryzowałeś, a która pochodzi z pliku README obcej osoby.

Ta strona stanowi model zagrożeń: opisuje, w jaki sposób kontrolowany przez atakującego tekst dociera do agenta działającego na serwerze, co atakujący zyskuje w każdym punkcie oraz które zabezpieczenia są warte wysiłku. Porady dotyczące izolacji zawarte w naszych pozostałych przewodnikach mają sens dopiero wtedy, gdy wiesz, przed czym dokładnie izolujesz agenta.

Dlaczego model nie potrafi oddzielić treści od instrukcji

Szkolenie pomaga, ale nie rozwiązuje problemu. Obecne modele są trenowane tak, aby traktować pobrany tekst z podejrzliwością, co pozwala im odrzucić wiele prymitywnych prób ataku. Odmowa wykonania polecenia jest jednak kwestią prawdopodobieństwa, a nie sztywną regułą. Atakujący może przeformułować zapytanie, ponowić próbę lub ukryć tekst w formacie, którego nikt nie przewidział; nie ma też żadnych ograniczeń co do liczby wariantów sformułowań, które może przetestować.

Projekt OWASP GenAI klasyfikuje to zjawisko jako LLM01:2025 Prompt Injection i dzieli je na dwa rodzaje. Wstrzyknięcie bezpośrednie (direct injection) ma miejsce, gdy użytkownik zmienia zachowanie modelu za pomocą własnego promptu. Wstrzyknięcie pośrednie (indirect injection) występuje, gdy treść zewnętrzna, na przykład ze strony internetowej lub pliku, zmienia zachowanie modelu podczas przetwarzania tych danych. To właśnie wstrzyknięcie pośrednie stanowi istotne zagrożenie na serwerze, ponieważ agent odczytuje znacznie więcej tekstu, niż użytkownik jest w stanie wpisać ręcznie.

Pierwszym systematycznym badaniem tego problemu jest praca Greshake'a i współpracowników pt. Not what you've signed up for (2023). Ich wniosek jest kluczowy: gdy aplikacja przekazuje pobrany tekst do modelu posiadającego dostęp do narzędzi, przetwarzanie takiego tekstu jest zbliżone do wykonania dowolnego kodu (arbitrary code execution).

Warunek przekształcający odczyt w naruszenie bezpieczeństwa

Samo odczytanie wrogiego tekstu nie stanowi szkody. Szkoda wymaga ścieżki wyjścia z maszyny.

Simon Willison nazwał to połączenie śmiertelną triadą w czerwcu 2025 roku. Agenta, który posiada dostęp do prywatnych danych, jest wystawiony na działanie niezaufanych treści i może wysyłać dane na zewnątrz, można nakłonić do przekazania pierwszych za pośrednictwem trzecich.

Agent programistyczny na Twoim VPS posiada wszystkie te cechy od pierwszego dnia. Prywatne dane to kod źródłowy, plik .env, klucze SSH oraz historia powłoki. Niezaufane treści to każde repozytorium, strona i wynik narzędzia, które agent odczytuje. Ścieżką wyjścia jest git push, curl, npm publish, treść pull requesta lub link wyświetlony w terminalu, w który klikniesz.

Nie można usunąć drugiego warunku, ponieważ odczytywanie niezaufanych tekstów jest zadaniem, do którego zatrudniono agenta. Dlatego każda praktyczna obrona skupia się na pozostałych dwóch.

Gdzie niezaufany tekst dociera do agenta programistycznego na serwerze

Repozytorium, w którym pracuje agent

Każdy plik w checkout jest daną wejściową. Komentarze w kodzie źródłowym, README.md, dzienniki zmian, testy, vendored code oraz same pliki instrukcji dla agenta: CLAUDE.md, AGENTS.md i ich odpowiedniki. Agent poproszony o zrozumienie bazy kodu odczytuje je, ponieważ o to właśnie poproszono.

Atakujący zyskuje tutaj dostęp do każdego, kto sklonuje repozytorium i skieruje na nie agenta. Pliki instrukcji są najbardziej bezpośrednią drogą, ponieważ istnieją po to, by być czytane jako instrukcje. Pull request, który dodaje cztery przydatne linie do CLAUDE.md oraz jedną linię przekierowującą agenta, jest zmianą, którą człowiek przeglądający kod pominie wzrokiem.

Zgłoszenia, pull requesty i komentarze do przeglądu kodu

Wszystko, co nieznajoma osoba wpisze w system śledzenia, dociera do agenta w momencie, gdy użytkownik poprosi go o analizę. W maju 2025 roku Invariant Labs opublikowało ustalenia dotyczące GitHub MCP o dokładnie takim charakterze. Agent programisty miał dostęp do jednego publicznego repozytorium oraz do repozytoriów prywatnych. Atakujący zgłosił problem w publicznym repozytorium. Gdy programista poprosił agenta o przejrzenie otwartych zgłoszeń, agent odczytał zawartość prywatnych repozytoriów i zapisał ją w pull requeście po stronie publicznej.

Ten raport opisuje problem architektoniczny, a nie błąd w kodzie serwera MCP. Agent posiadał jeden szeroki token dostępu, odczytywał dane z publicznej skrzynki odbiorczej i miał uprawnienia do zapisu. W typowym rozumieniu nic nie było błędnie skonfigurowane, dlatego rozwiązaniem jest ograniczenie zakresu uprawnień, a nie łatanie kodu.

Strony internetowe pobierane przez agenta

Dokumentacja, odpowiedzi na forach, strona dostawcy, wynik wyszukiwania. Każdy z tych elementów może zawierać tekst napisany dla agenta, a nie dla użytkownika. HTML przekonwertowany na tekst daje atakującemu dodatkowe pole manewru, ponieważ treść, której przeglądarka nigdy nie wyświetla, i tak dociera do modelu.

Atakujący przejmuje kontrolę w momencie, gdy agent jest najmniej obserwowany. Nikt nie czyta pełnego tekstu strony, którą agent pobrał w trakcie wyszukiwania informacji.

Dane wyjściowe narzędzi MCP

MCP (model context protocol) to powszechny sposób łączenia agentów z zewnętrznymi narzędziami. Wyniki wracają jako tekst i trafiają bezpośrednio do okna kontekstowego. Istnieją tutaj dwie płaszczyzny, a nie jedna. Dane zwracane przez narzędzie są oczywiste. Nazwa i opis narzędzia, które model odczytuje, aby zdecydować, kiedy je wywołać, to druga płaszczyzna, a serwer, nad którym nie ma się kontroli, może zmienić obie te wartości między wywołaniami.

Atakujący, który umieści tekst w danych wyjściowych jednego narzędzia, dociera do każdego innego narzędzia, do którego agent ma dostęp. W ten sposób wstrzyknięcie kodu w mało istotnym elemencie kończy się sterowaniem czymś o wysokiej wartości.

Logi CI, dane wyjściowe budowania i metadane zależności

npm install wypisuje tekst z pakietów, których użytkownik nie napisał. Błąd testu wypisuje komunikat asercji z biblioteki. Log zadania ciągłej integracji (CI) to tysiące linii danych wyjściowych stron trzecich. Poproś agenta o naprawę nieudanego budowania, a odczyta on całość.

W tym przypadku atakujący zyskuje dostęp do maszyny budującej, która zazwyczaj przechowuje poświadczenia wdrożeniowe i tokeny rejestrów, a poświęca się jej mniej uwagi niż laptopowi.

Co faktycznie uzyskuje atakujący

Warto zaplanować reakcję na cztery możliwe scenariusze.

Kradzież poświadczeń. Wszystko, co może odczytać proces agenta, jest zagrożone: zmienne środowiskowe, ~/.aws/credentials, ~/.ssh, token gh czy plik konfiguracyjny Docker. Wysłanie tych danych na zewnątrz nie wymaga curl. Dane mogą zostać wyprowadzone z serwera poprzez commit do gałęzi, opis pull requesta, publikację pakietu w rejestrze lub zapytanie DNS o domenę kontrolowaną przez atakującego.

Zmiany w kodzie zatwierdzone przez użytkownika. Pisanie kodu jest podstawową funkcją agenta, więc nakłonienie go do dodania subtelnie błędnej linii jest najłatwiejszym do osiągnięcia celem. Może to być dodana zależność lub wywołanie logowania, które przesyła token do dziennika wysyłanego w inne miejsce.

Utrzymanie dostępu. Raz zapisany plik działa bez udziału modelu: hook w .git/hooks, skrypt postinstall w package.json, linia dopisana do pliku startowego powłoki lub dodatkowa linia w CLAUDE.md. Kolejne polecenie uruchomi ten kod.

Ruch wewnątrz sieci. Agent działa tam, gdzie został umieszczony. Jeśli ten serwer ma dostęp do bazy danych przez loopback, wewnętrznej usługi administracyjnej, usługi metadanych dostawcy chmury lub innego hosta w sieci prywatnej, to samo dotyczy każdego procesu sterującego agentem.

Tryby automatycznego zatwierdzania usuwają ostatni etap weryfikacji

W trybie domyślnym Claude Code prosi o potwierdzenie przed wykonaniem polecenia lub edycją pliku. To zapytanie stanowi ludzką weryfikację, która znajduje się pomiędzy wszystkimi powyższymi operacjami a rzeczywistym działaniem. Tryby, które usuwają to zapytanie, usuwają również weryfikację.

Dokumentacja jest jednoznaczna w kwestii bypassPermissions: należy go używać wyłącznie w odizolowanych środowiskach, takich jak kontenery lub maszyny wirtualne, w których Claude Code nie może wyrządzić szkód. Tryb auto jest mniej restrykcyjny i automatycznie zatwierdza wywołania narzędzi przy użyciu działających w tle mechanizmów bezpieczeństwa, które sprawdzają, czy działania są zgodne z żądaniem użytkownika. Mechanizmy te wykrywają wiele zagrożeń. Nadal jednak opierają się na ocenie modelu dotyczącej danych wyjściowych modelu, dlatego należy traktować je jako filtr, a nie jako barierę ochronną.

Administrator może usunąć oba te zabezpieczenia. Należy ustawić permissions.disableBypassPermissionsMode lub permissions.disableAutoMode na "disable" w pliku ustawień i umieścić ten plik w ustawieniach zarządzanych, aby pobrany projekt nie mógł go nadpisać. Nasz przewodnik po trybie automatycznym Claude Code i regułach uprawnień zawiera informacje o tym, gdzie każda z reguł znajduje zastosowanie.

Obrona w zależności od zysków

Żadne z poniższych rozwiązań nie jest ostatecznym zabezpieczeniem. Każde z nich ogranicza zakres danych dostępnych dla agenta lub zakres jego możliwości.

  1. Maszyna, którą można zniszczyć i odtworzyć, dzięki czemu naruszenie bezpieczeństwa kosztuje godzinę pracy, a nie jest poważnym incydentem.
  2. Poświadczenia oddzielone od osobistych, ograniczone do jednego repozytorium i krótkotrwałe.
  3. Brak długotrwałych sekretów w środowisku, z którego dziedziczone są polecenia agenta.
  4. Wymuszenie na poziomie systemu operacyjnego ograniczeń ruchu wychodzącego i dostępu do plików, stosowane do każdego procesu uruchamianego przez agenta.
  5. Włączone monity o zatwierdzenie dla operacji zapisu i połączeń sieciowych.
  6. Hooki jako deterministyczna bariera dla konkretnych, zdefiniowanych działań.
  7. Przeglądanie diff przed scaleniem zmian.

Kolejność ma znaczenie. Punkty od 1 do 4 działają nawet wtedy, gdy model jest w pełni kontrolowany przez atakującego. Punkty od 5 do 7 zależą od uwagi człowieka, która zanika podczas długotrwałej pracy agenta.

Umieszczenie agenta na maszynie, którą można usunąć

VPS zawierający checkout i jeden ograniczony token jest znacznie mniejszym łupem niż laptop z Twoimi kluczami. Uruchamiaj agenta jako osobnego użytkownika bez uprawnień, a nie jako swoje konto użytkownika czy root. Nasze przewodniki dotyczące jednorazowej maszyny wirtualnej dla agentów programistycznych oraz użytkowników z minimalnymi uprawnieniami na VPS opisują konfigurację, a bezpieczne uruchamianie Claude Code na VPS omawia codzienne praktyki.

Usunięcie sekretów ze środowiska

Zmienna środowiskowa jest czytelna dla każdego procesu potomnego, co oznacza, że widzi ją każde polecenie uruchamiane przez agenta. Piaskownica Claude Code może usuwać wybrane zmienne przed każdym poleceniem. W systemie Linux piaskownica wymaga najpierw dwóch pakietów:

sudo apt-get install bubblewrap socat

Następnie w ~/.claude/settings.json:

{
  "sandbox": {
    "enabled": true,
    "network": {
      "allowedDomains": ["github.com", "*.npmjs.org"]
    },
    "credentials": {
      "envVars": [
        { "name": "GITHUB_TOKEN", "mode": "deny" },
        { "name": "NPM_TOKEN", "mode": "deny" }
      ]
    }
  }
}

Wpis deny usuwa daną zmienną przed uruchomieniem każdego polecenia w piaskownicy, a allowedDomains ogranicza polecenia w piaskownicy do wymienionych hostów. Blok credentials wymaga Claude Code w wersji 2.1.187 lub nowszej (stan na sierpień 2026). Uruchom /sandbox w sesji, aby sprawdzić, które warstwy są aktywne i których zależności brakuje. Decyzja o tym, które sekrety w ogóle muszą znajdować się na danej maszynie, stanowi większą część zadania, co opisano w utrzymywaniu sekretów poza zasięgiem agenta AI.

Ograniczenie ruchu wychodzącego na poziomie systemu operacyjnego

Reguła firewalla nie bierze pod uwagę decyzji modelu. Uruchom agenta jako dedykowanego użytkownika agent, a następnie odrzucaj ruch wysyłany przez tego użytkownika:

table inet agentcage {
  chain output {
    type filter hook output priority filter; policy accept;
    meta skuid "agent" ct state established,related accept
    meta skuid "agent" oif lo accept
    meta skuid "agent" counter drop
  }
}

Pozostawia to użytkownika agent z dostępem tylko do loopback, więc jego ruch musi przechodzić przez proxy uruchomione na tej samej maszynie, a proxy posiada listę dozwolonych nazw hostów. Dzięki https_proxy wskazującemu na to proxy, klient wysyła żądanie CONNECT, a proxy wykonuje wyszukiwanie nazwy, więc agent nie potrzebuje własnego dostępu do DNS (domain name system). Sprawdź poprawność konfiguracji za pomocą sudo nft list ruleset i obserwuj wzrost licznika reguły odrzucania, gdy agent próbuje połączyć się z nowym zasobem.

Podczas wprowadzania zmian w firewallu utrzymuj otwartą drugą sesję SSH. Sprawdź również, jak środowisko uruchomieniowe kontenerów wpływa na te reguły: Docker tworzy własne łańcuchy, a opublikowane porty Docker omijają ufw opisuje związane z tym ryzyko.

Hooki: kontrola, której model nie może obejść

Reguły uprawnień i hooki są wymuszane przez Claude Code, a nie przez model. Dokumentacja stwierdza to jasno: instrukcje w Twoim prompcie lub CLAUDE.md kształtują to, co Claude próbuje zrobić, ale nie zmieniają tego, na co pozwala Claude Code. To rozróżnienie stanowi o wartości tego rozwiązania. Linia w CLAUDE.md o treści "never run curl" jest sugestią, z którą wstrzyknięty akapit może polemizować. Hook to proces, który zwraca kod wyjścia.

Zarejestruj hook PreToolUse w .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/no-egress.sh"
          }
        ]
      }
    ]
  }
}

Hook otrzymuje wywołanie narzędzia jako JSON na standardowym wejściu. Kod wyjścia 2 blokuje wywołanie i pokazuje Claude przyczynę ze standardowego wyjścia błędów. Kod wyjścia 0 pozwala na kontynuowanie wywołania zgodnie z normalnym przepływem uprawnień.

#!/usr/bin/env bash
# PreToolUse: stdin holds the tool call, exit 2 blocks it.
cmd=$(jq -r '.tool_input.command // ""')
if printf '%s' "$cmd" | grep -qE '(^|[;&|]|\s)(curl|wget|nc|ncat)(\s|$)'; then
  echo "Blocked: this repository does not allow outbound network commands." >&2
  exit 2
fi
exit 0

A teraz szczera prawda. Jest to lista zabronionych elementów oparta na ciągu znaków powłoki, a takie listy są nieszczelne. python3 -c otwiera gniazdo bez użycia słowa curl. Cel make deploy ukrywa to samo wywołanie o jeden poziom głębiej. Pisz hooki dla błędów, które potrafisz nazwać, a granice, na których faktycznie polegasz, umieszczaj w jądrze systemu lub w sieci.

Reguły odmowy uprawnień mają limit dopasowania, o którym warto wiedzieć. Reguły odmowy Read i Edit obejmują własne narzędzia plikowe Claude oraz polecenia plikowe rozpoznawane w Bash, takie jak cat, head, tail i sed. Nie obejmują one skryptów Python lub Node, które same otwierają plik. Reguły są oceniane w kolejności: najpierw odmowa, potem zapytanie, potem zezwolenie, więc reguła odmowy nie może zawierać wyjątku w postaci listy dozwolonych elementów.

{
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(./secrets/**)",
      "Bash(git push *)"
    ]
  }
}

Monitorowanie ruchu i czytanie diff

Działanie agenta generuje diff oraz zestaw wywołań sieciowych. Oba elementy wymagają weryfikacji przed scaleniem lub wdrożeniem zmian. Samodzielnie hostowany przegląd bezpieczeństwa diffa wykrywa inny rodzaj zmian niż pobieżne przejrzenie przez człowieka, a wiedza o tym, co agent programistyczny wysyła poza maszynę pozwala zrozumieć, jak wygląda normalny ruch, dzięki czemu nietypowe żądanie staje się widoczne.

Co pozostaje nierozwiązane

Obecnie nie istnieje niezawodny sposób na oddzielenie treści od instrukcji. Każde zabezpieczenie, które trafia do wydań, jest albo filtrem o określonym wskaźniku błędów, albo ograniczeniem skutków ataku. W obecnym stosie technologicznym nie ma mechanizmu oznaczającego fragment tekstu jako dane, które nigdy nie mogą zostać wykonane jako polecenie.

Filtry są pomocne, ale zawodzą. Klasyfikator, który wyłapuje większość prób iniekcji, musi być skuteczny za każdym razem, podczas gdy atakujący musi odnieść sukces tylko raz. Ta asymetria sprawia, że opublikowany wskaźnik skuteczności zabezpieczenia jest punktem wyjścia dla kolejnej próby ataku, a nie gwarancją bezpieczeństwa.

Najbardziej obiecujące prace koncentrują się na poziomie projektowania, a nie na poziomie modelu. CaMeL, opisany w Defeating Prompt Injections by Design (Debenedetti i współpracownicy, 2025), najpierw wyodrębnia przepływ sterowania i danych z zaufanego żądania, dzięki czemu niezaufane dane nie mogą zmienić działania programu, a następnie wymusza sprawdzanie uprawnień podczas wywoływania narzędzi. Rysunki zawarte w publikacji, oparte na benchmarku AgentDojo, pokazują koszt tego rozwiązania.

ChartAgentDojo tasks solved, published figures from the CaMeL paper (2025)
The data behind this chart
[
  {
    "label": "Undefended agent",
    "tasks_solved_pct": 84
  },
  {
    "label": "CaMeL",
    "tasks_solved_pct": 77
  }
]

Niezabezpieczony agent rozwiązał 84 procent zadań. CaMeL rozwiązał 77 procent zadań, zapewniając przy tym gwarancję bezpieczeństwa. Są to dane opublikowane w pracy dla jednego benchmarku, a nie pomiar dla konkretnego obciążenia roboczego. Różnica między tymi wartościami stanowi przybliżony koszt realnej gwarancji w dzisiejszych warunkach.

Dopóki rozwiązania tego typu nie trafią do narzędzi używanych na co dzień, należy zakładać, że agent może zostać przejęty, i dążyć do tego, aby takie zdarzenie było nieistotne. To główny argument za stosowaniem maszyn jednorazowych, ograniczonych uprawnień, kontrolowanego ruchu wychodzącego oraz nawyku weryfikacji zmian w kodzie (diff).

FAQ

Czy mogę powstrzymać wstrzykiwanie promptów (prompt injection), nakazując agentowi ignorowanie instrukcji zawartych w plikach?

Nie. Takie zdanie jest tekstem znajdującym się w tym samym oknie kontekstowym co atak i konkuruje z tekstem atakującego na równych zasadach. Dokumentacja Claude Code jasno wyznacza granicę: instrukcje w prompcie lub CLAUDE.md kształtują to, co agent próbuje zrobić, ale nie zmieniają tego, na co pozwala narzędzie. Plik z instrukcjami należy traktować jako deklarację intencji, a wszystko, na czym polegasz, umieścić w regułach uprawnień, hooku PreToolUse lub regule firewalla.

Czy wstrzykiwanie promptów stanowi realne ryzyko, jeśli agent operuje tylko na moim własnym repozytorium?

Tak, ponieważ repozytorium jest pełne tekstu, którego nie napisałeś. Pliki README zależności, adresy URL w plikach blokad (lockfiles), dane testowe, kod zewnętrzny (vendored code) oraz dane wyjściowe npm install – wszystko to trafia do systemu podczas zwykłego zadania. Wszelkie dane pobrane z systemu śledzenia zgłoszeń lub strony z dokumentacją trafiają tam w ten sam sposób. Ryzyko rośnie wraz z ilością danych odczytywanych przez agenta, a użyteczny agent odczytuje ich bardzo dużo.

Czy uruchomienie agenta w kontenerze rozwiązuje ten problem?

Ogranicza ono szkody, ale tylko pod warunkiem odebrania mu również poświadczeń. Kontener z przekazanym agentem SSH, poświadczeniami chmurowymi w zmiennych środowiskowych i nieograniczonym dostępem do sieci daje atakującemu niemal wszystko to, co host. To, co faktycznie zyskujesz dzięki kontenerowi, to system plików, który można usunąć, oraz czyste środowisko do egzekwowania reguł ruchu wychodzącego (egress rules). Połącz to z tokenem ograniczonym do jednego repozytorium.

Jaka pojedyncza zmiana najbardziej redukuje ryzyko?

Usuń długożyjące poświadczenia ze środowiska, z którego dziedziczą polecenia agenta, a następnie nadaj tej maszynie domyślną politykę blokowania ruchu wychodzącego (default-deny egress policy). Razem przełamują one trzeci warunek zabójczego tria: tekst nadal może przejąć kontrolę nad agentem, ale dane, do których uzyskuje on dostęp, nie mają dokąd trafić. Prompty zatwierdzające i przegląd różnic (diff review) również pomagają, jednak zależą one od czujności człowieka podczas długotrwałej pracy, dlatego znajdują się w hierarchii ważności poniżej tych dwóch zmian.