Prompt injection w PDF pod RAG: jak wykryć i zneutralizować atak, zanim dokument trafi do indeksu
Praktyczny przewodnik po prompt injection w PDF dla RAG: typowe wzorce ataku, bezpieczne wydobycie tekstu, separacja instrukcji od danych, skanery i red-team testy przed indeksacją.
Czym jest prompt injection w PDF i dlaczego RAG jest na to podatny?
Prompt injection w PDF to celowe umieszczenie w treści dokumentu instrukcji sformułowanych tak, aby po „przeczytaniu” przez model językowy wpłynęły one na jego zachowanie, mimo że nie pochodzą od użytkownika ani od twórcy systemu. W praktyce w PDF mogą to być pozornie niewinne fragmenty tekstu (np. w treści, przypisach, nagłówkach, komentarzach, białym tekście na białym tle lub w elementach wyciąganych przez OCR), które wyglądają jak polecenia dla asystenta, np. aby zignorował wcześniejsze zasady, ujawnił poufne informacje albo zmienił sposób odpowiadania.
RAG (Retrieval-Augmented Generation) jest na to podatny, ponieważ jego kluczowym krokiem jest dołączanie fragmentów dokumentów jako kontekstu do promptu modelu. Model nie ma natywnej, pewnej metody rozróżnienia, czy dany tekst w kontekście jest „danymi do analizy” (treścią źródłową), czy „instrukcją sterującą” (poleceniem). Jeżeli złośliwe instrukcje trafią do pobranego kontekstu, mogą konkurować z instrukcjami systemowymi lub użytkownika i modyfikować odpowiedź, zwłaszcza gdy są sformułowane autorytatywnie (np. „SYSTEM: …”, „IMPORTANT: …”) albo wykorzystują typowe dla LLM wzorce poleceń.
Istotne jest też to, że w RAG ryzyko pojawia się już na etapie indeksowania i późniejszego wyszukiwania: dokument jest dzielony na fragmenty i zamieniany na embeddingi, a następnie dobierany semantycznie do zapytań. To oznacza, że zainfekowany fragment może być zwrócony jako „najbardziej pasujący” do pytania użytkownika i automatycznie dołączony do promptu, nawet jeśli użytkownik nie prosił o instrukcje, tylko o informację merytoryczną. W efekcie atak nie wymaga bezpośredniego dostępu do interfejsu czatu — wystarczy, że złośliwy PDF znajdzie się w korpusie dokumentów używanym przez RAG.
Jakie wzorce tekstu i formatowania w PDF najczęściej wskazują na próbę prompt injection?
W PDF pod RAG podejrzane są fragmenty, które nie wyglądają jak treść merytoryczna dokumentu, tylko jak instrukcje sterujące zachowaniem modelu lub retrievera. Najczęściej mają formę rozkazującą i „meta-język” typowy dla promptów: ignore previous instructions, follow these rules, system/developer/user, you are ChatGPT, prośby o ujawnienie konfiguracji, kluczy, polityk, albo nakazy pominięcia zasad bezpieczeństwa. Charakterystyczne są też instrukcje odnoszące się do samego procesu RAG, np. polecenia, by traktować dany fragment jako najwyższy priorytet, nadpisać kontekst, zignorować inne źródła albo „przepisać” odpowiedź w określony sposób niezależnie od pytania użytkownika.
Drugą grupą sygnałów są wzorce „udające” strukturę danych lub poleceń, które model ma bezkrytycznie wykonać: bloki w ```, pseudo-konfiguracje (YAML/JSON), sekcje opisane jako SYSTEM PROMPT, INSTRUCTIONS, POLICY, a także ciągi typu „krok 1/2/3” nastawione na kontrolę odpowiedzi, a nie na opis tematu dokumentu. Podejrzane bywa również nadmierne stosowanie słów-kluczy typu IMPORTANT, DO NOT, MUST, „priorytet”, „nadrzędne”, zwłaszcza gdy nie pasują do stylistyki reszty treści.
Po stronie formatowania w PDF często alarmujące są techniki ukrywania lub wymuszania widoczności instrukcji. Należą do nich: bardzo mała czcionka, kolor tekstu zbliżony do tła (np. biały na białym), tekst umieszczony poza obszarem strony, pod innymi elementami lub jako warstwa nakładkowa, a także ekstremalne odstępy i nietypowe łamanie linii, które utrudniają zauważenie instrukcji w podglądzie, ale nadal trafiają do ekstrakcji tekstu. Częste są też „doklejone” nagłówki/stopki, komentarze w treści przypominające watermark albo przypisy, które zawierają instrukcje, mimo że semantycznie nie wnoszą wartości do dokumentu.
W praktyce najbardziej podejrzane są kombinacje: meta-instrukcje o priorytetach wraz z formatowaniem sugerującym ukrycie (np. drobny, blady tekst w stopce) albo fragmenty, które po ekstrakcji tworzą spójny prompt, mimo że wizualnie są porozrzucane. Tego typu wzorce rzadko mają uzasadnienie w zwykłej dokumentacji i często wskazują na próbę wpływania na zachowanie modelu poprzez treść PDF.
Jak bezpiecznie wyciągać tekst z PDF, żeby nie przenieść ukrytych instrukcji do indeksu?
Kluczowe jest traktowanie PDF jako kontenera z wieloma warstwami treści, a nie „czystego tekstu”. Ataki prompt injection w PDF często wykorzystują elementy niewidoczne dla czytelnika (np. tekst o zerowej wielkości fontu, biały na białym tle, warstwy/Optional Content Groups, adnotacje, pola formularzy, metadane, osadzone pliki), które mogą zostać wydobyte przez parser i następnie trafić do indeksu RAG. Bezpieczne wydobycie polega więc na celowym ograniczeniu źródeł tekstu do tego, co jest rzeczywiście renderowane jako treść dokumentu, oraz na odrzuceniu kanałów „pobocznych”, w których najłatwiej ukryć instrukcje.
W praktyce najbezpieczniejsza ścieżka to ekstrakcja z warstwy renderowanej: renderujesz strony do obrazu i uruchamiasz OCR, a do indeksu zapisujesz wyłącznie wynik OCR (ewentualnie z zachowaniem informacji o stronie). Taki pipeline ignoruje większość ukrytych warstw i obiektów PDF, bo opiera się na tym, co faktycznie widać. Jeśli OCR jest zbyt kosztowny, alternatywą jest ekstrakcja „layout-aware” ograniczona do tekstu widocznego: parser powinien brać pod uwagę pozycję i styl tekstu oraz odfiltrowywać elementy niewidoczne (np. poza obszarem strony, zakryte, o zerowej/śladowej wielkości, z trybem renderowania niewyświetlającym) oraz wszystko, co nie jest główną treścią strony.
Niezależnie od metody, w procesie indeksowania ustaw twardą separację kanałów: indeksuj tylko body tekstu z treści strony, a metadane, adnotacje, komentarze, pola formularzy, załączniki i inne strumienie traktuj jako nieufne i domyślnie wykluczaj. Jeśli z powodów biznesowych musisz je zachować, przechowuj je poza indeksem wyszukiwawczym jako osobne pola z jasną etykietą pochodzenia, żeby nie mieszać ich z treścią, którą model ma traktować jako wiedzę domenową.
Dobrym zabezpieczeniem jest też normalizacja i „detoksykacja” wyjścia z ekstraktora przed indeksowaniem: usuń znaki kontrolne i nietypowe separatory, ujednolić białe znaki, ogranicz długość pojedynczych bloków, a treść podziel na chunki po granicach akapitów/sekcji. Takie przygotowanie utrudnia przemycenie instrukcji poprzez niewidoczne znaki lub zlepianie fragmentów w jeden, bardzo długi prompt.
Na koniec utrzymuj rozdział zaufania w samym pipeline: ekstraktor powinien działać w izolowanym środowisku (bez wykonywania skryptów, bez pobierania zasobów zewnętrznych), a do indeksu powinien trafiać tylko wynik po filtrach widoczności i po sanityzacji. Dzięki temu minimalizujesz ryzyko, że ukryte instrukcje z PDF zostaną potraktowane jako treść do wyszukiwania i w konsekwencji zostaną „wstrzyknięte” do kontekstu modelu przez RAG.
Jak oddzielić instrukcje od treści i wymusić, żeby model traktował dokument tylko jako dane?
Kluczowe jest rozdzielenie ról: instrukcje mogą pochodzić wyłącznie z zaufanych kanałów (system/developer lub kontrolowana logika aplikacji), a dokument musi zostać podany modelowi wyłącznie jako cytowany materiał źródłowy do analizy. W praktyce oznacza to, że treść PDF nie powinna być wstrzykiwana do tej samej „warstwy znaczeniowej” co polecenia, tylko zamykana w wyraźnym kontenerze danych (np. jako pole „context”, cytat lub blok oznaczony jako materiał referencyjny), z jednoznacznym zakazem wykonywania poleceń znalezionych w tym materiale.
Najskuteczniejszy wzorzec to jawne „instrukcje o instrukcjach”: w wiadomości systemowej/deweloperskiej określasz, że jedynymi wiążącymi poleceniami są te z warstwy system/developer, a wszystko z dokumentu ma być traktowane jako nieufne dane, nawet jeśli wygląda jak prompt. W tej samej instrukcji warto zdefiniować regułę kolizji: gdy dokument próbuje nadpisać zasady (np. „zignoruj powyższe”, „odtajnij sekrety”, „wykonaj te kroki”), model ma to sklasyfikować jako treść dokumentu i pominąć w sensie wykonawczym, ewentualnie przytoczyć jako cytat lub opisać jako próbę instrukcji w źródle.
Technicznie pomaga też twarde odseparowanie wejść w formacie danych: przekazywanie dokumentu jako osobnego pola (np. JSON: {"question":..., "context":...}), używanie jawnych delimiterów oraz wymaganie, by odpowiedź opierała się wyłącznie na treści kontekstu z cytowaniem. To nie jest „magiczna ochrona”, ale ogranicza ryzyko, że model potraktuje fragmenty dokumentu jako priorytetowe polecenia, bo widzi wyraźny podział na część sterującą i część referencyjną.
Najważniejsze ograniczenie: nie da się „wymusić” tego w 100% samą składnią promptu, bo model nadal interpretuje tekst probabilistycznie. Dlatego rozdzielenie instrukcji od danych powinno być realizowane zarówno w warstwie promptu (hierarchia i reguły), jak i w warstwie aplikacyjnej (osobne pola/wiadomości dla kontekstu, kontrola tego, co trafia do roli system/developer oraz brak możliwości, by treść z PDF stała się instrukcją sterującą).
Jakie heurystyki i automatyczne skanery warto wdrożyć przed dodaniem dokumentu do bazy wiedzy?
Przed indeksowaniem dokumentu pod RAG warto zastosować dwie warstwy kontroli: heurystyki (reguły wykrywające typowe wzorce prompt injection w treści) oraz automatyczne skanery (narzędzia analizujące plik i jego warstwy techniczne). Celem nie jest „udowodnienie” ataku, tylko szybkie odseparowanie materiałów o podwyższonym ryzyku do ręcznej weryfikacji albo do bezpieczniejszego trybu ekstrakcji.
Po stronie heurystyk kluczowe są reguły językowe i strukturalne: wykrywanie metainstrukcji kierowanych do modelu (np. próby narzucenia roli, polecenia ignorowania wcześniejszych zasad, żądania ujawnienia promptu/systemu lub danych z innych źródeł), fraz typu „assistant/model/system/developer”, wzorców nakazujących wykonanie działań niezwiązanych z dokumentem (np. „wyślij”, „połącz się”, „zadzwoń”, „pobierz z URL”) oraz anomalii w formatowaniu, które często maskują instrukcje (nagłe przejścia do monologu imperatywów, sekcje „INSTRUCTION/IMPORTANT”, nietypowo wysoki udział wersalików, powtarzalność komend, tekst o niskiej spójności merytorycznej w stosunku do tematu pliku). Skuteczne są także heurystyki kontekstowe: porównanie tematu dokumentu z treścią podejrzanych fragmentów i oznaczanie tych, które są „meta” (o zachowaniu modelu) zamiast „content” (o domenie).
Po stronie automatycznych skanerów warto objąć kontrolą zarówno warstwę „widoczną” (tekst po ekstrakcji), jak i warstwy techniczne PDF/Office: wykrywanie ukrytych warstw tekstu (tekst poza obszarem strony, biały na białym, minimalna czcionka, elementy z bardzo niską przezroczystością), rozbieżności między tekstem selekcjonowalnym a renderowanym (co widzi człowiek vs co wyciąga parser), obecności osadzonych plików/załączników, formularzy, JavaScriptu, linków oraz nietypowych obiektów (np. duża liczba obiektów o małych rozmiarach). Niezależnie od prompt injection, skaner antywirusowy i sandbox dla załączników pomagają odsiać dokumenty z klasycznymi zagrożeniami, a detekcja PII/sekretów (np. klucze API, tokeny, hasła) ogranicza ryzyko, że model zacznie „pracować” na wrażliwych danych w indeksie.
Praktycznie najlepsze rezultaty daje połączenie: (1) skanów struktury pliku i obiektów, (2) ekstrakcji tekstu co najmniej dwiema metodami (np. render-based OCR vs parser tekstu) i porównania wyników, oraz (3) reguł heurystycznych na finalnym tekście, który rzeczywiście trafi do indeksu. Jeśli którykolwiek etap podnosi ryzyko, dokument powinien trafić do kolejki weryfikacji lub zostać zindeksowany dopiero po sanitizacji podejrzanych fragmentów.
Jak testować podatność na prompt injection w praktyce metodą red-team przed produkcją?
Red-teaming prompt injection to kontrolowane, powtarzalne „atakowanie” kompletnego przepływu RAG (ingest PDF → ekstrakcja tekstu → chunking → indeks → retrieval → generacja) tak, jak zrobi to napastnik, a następnie mierzenie, czy model wykonuje polecenia zaszyte w dokumencie lub ujawnia dane, których nie powinien. Kluczowe jest testowanie na środowisku jak najbardziej zbliżonym do produkcji: te same modele, te same ustawienia systemowe, te same filtry, ten sam retriever i te same ograniczenia uprawnień narzędzi, bo prompt injection często „działa” dopiero w konkretnych konfiguracjach kontekstu i narzędzi.
W praktyce zaczyna się od zdefiniowania mierzalnych celów testu i kryteriów porażki, czyli tego, co uznajecie za udany atak. Dla RAG na PDF typowe kryteria to: model wykonuje instrukcje pochodzące z treści dokumentu mimo sprzeczności z instrukcją systemową; model wycieka informacje spoza dozwolonego kontekstu (np. treści system promptu, polityk, kluczy, wewnętrznych identyfikatorów); model generuje odpowiedź, która wygląda na zgodną z dokumentem, ale w rzeczywistości jest skutkiem „przejęcia” toku rozumowania; model inicjuje nieautoryzowane użycie narzędzi (np. wyszukiwanie, pobieranie, zapytania do API) lub formułuje prośby o dane wrażliwe. Takie kryteria trzeba zapisać przed testem, aby wynik był jednoznaczny.
Następnie buduje się zestaw próbek atakujących jako pliki PDF i jako zapytania użytkownika. W dokumentach warto umieszczać instrukcje w miejscach, które typowo trafiają do indeksu i bywają przywoływane w retrieval: tytuły, stopki, sekcje „uwagi”, przypisy, treści w tabelach, wstawki „instrukcja dla asystenta”, a także tekst ukryty w warstwach PDF (np. bardzo mała czcionka, biały tekst na białym tle) i w metadanych, jeśli pipeline je ekstraktuje. Równolegle przygotowuje się zapytania użytkownika, które sprowokują retrieval chunków z payloadem, bo prompt injection w RAG zwykle wymaga, by złośliwy fragment znalazł się w kontekście odpowiedzi.
Kluczowym elementem red-teamu jest uruchamianie testów w scenariuszach zróżnicowanych pod kątem tego, jak łatwo payload „dostaje się” do kontekstu. Trzeba testować różne długości kontekstu, różne strategie chunkingu (wielkość, overlap), różne parametry top-k i rerankingu oraz sytuacje, w których do kontekstu trafia zarówno złośliwy fragment, jak i „bezpieczne” fragmenty o wysokiej relewantności. Celem jest sprawdzenie, czy model potrafi zignorować instrukcje z dokumentu nawet wtedy, gdy są one sformułowane autorytatywnie („to jest polityka bezpieczeństwa”), technicznie („użyj narzędzia”), lub gdy podszywają się pod system/deweloper prompt.
Aby wynik był wiarygodny, każdą próbę należy instrumentować i logować w sposób umożliwiający analizę przyczyn. Minimalny zestaw artefaktów to: dokładna treść zapytania użytkownika, pełny kontekst przekazany do modelu (z zaznaczeniem, które chunki pochodziły z jakiego dokumentu i miejsca), odpowiedź modelu oraz informacja, czy i jakie narzędzia zostały wywołane. Bez tych danych nie da się odróżnić „model halucynuje” od „model wykonał instrukcję z dokumentu”, ani ustalić, czy problem powstał na etapie ekstrakcji PDF, indeksowania czy generacji.
Ocena skuteczności ataku powinna być możliwie obiektywna. Najlepiej stosować automatyczne asercje (reguły) powiązane z wcześniej zdefiniowanymi kryteriami porażki, np. wykrycie wzorców ujawnienia danych (sekrety, prompt systemowy), obecności zakazanych fraz typu „zignorowałem instrukcje”, wykonania niedozwolonego wywołania narzędzia, albo odpowiedzi, która zawiera treści nieobecne w dozwolonym kontekście. Tam, gdzie nie da się tego rozstrzygnąć regułami, stosuje się kontrolowaną ocenę ręczną na ograniczonym wycinku przypadków, ale nadal z jasnym protokołem: co dokładnie uznaje się za „posłuchanie dokumentu”.
Wynik red-teamu nie powinien kończyć się na „działa/nie działa”, tylko na liście reprodukowalnych przypadków z minimalnym zestawem kroków, które wywołują podatność. Każdy znaleziony przypadek warto sprowadzić do najmniejszego payloadu w PDF i najprostszego zapytania użytkownika, które nadal przełamuje zabezpieczenia. Taka minimalizacja przyspiesza poprawki i pozwala zbudować regresyjny zestaw testów, który uruchamiacie przed wdrożeniem oraz po każdej zmianie modelu, promptu, retrievera lub pipeline’u ekstrakcji PDF.
Co zrobić, gdy wykryję podejrzany dokument: kwarantanna, poprawki, wersjonowanie i ślad audytowy?
Po wykryciu podejrzenia prompt injection lub innej manipulacji traktuj dokument jak incydent: natychmiast przerwij jego przepływ do indeksu/RAG i odizoluj go w kontrolowanej kwarantannie. Kwarantanna powinna oznaczać brak dostępu dla pipeline’u ingestii (brak OCR/parsowania do wektorów), ograniczony dostęp tylko dla osób uprawnionych oraz zachowanie oryginału w stanie niezmienionym (do analizy i dowodów).
W kwarantannie wykonaj minimalne, kontrolowane działania: (1) zabezpiecz kopię referencyjną pliku (hash, metadane, źródło pozyskania), (2) przeanalizuj podejrzane fragmenty w treści i metadanych, (3) zdecyduj, czy dokument można naprawić, czy należy go odrzucić. Jeśli dokument ma zostać naprawiony, poprawki muszą polegać na usunięciu/zneutralizowaniu wyłącznie szkodliwych instrukcji (np. ukrytych poleceń w treści, adnotacjach, warstwach), bez niekontrolowanego „przepisywania” merytoryki. Po naprawie dokument przechodzi ponowną walidację tym samym procesem, który go oznaczył jako podejrzany.
Wersjonowanie jest krytyczne, bo indeks/RAG nie może „pamiętać” niezweryfikowanej wersji. Utrzymuj co najmniej trzy stany: oryginał (read-only), wersja robocza (do sanitizacji) oraz wersja zatwierdzona (jedyna dopuszczona do indeksowania). Każda wersja powinna mieć jednoznaczny identyfikator, datę, autora zmiany oraz powiązanie z decyzją (np. „odrzucony”, „zatwierdzony po sanitizacji”). Jeśli podejrzana wersja zdążyła trafić do indeksu, konieczne jest jej wycofanie (reindeksacja/aktualizacja) i zastąpienie wersją zatwierdzoną, tak aby pipeline nie serwował wyników z zakażonego materiału.
Ślad audytowy (audit trail) ma dać rozliczalność i możliwość odtworzenia decyzji: co wykryto, kto podjął działania, jakie zmiany wykonano i jaka wersja została ostatecznie wprowadzona do indeksu. Minimalny audyt powinien obejmować: identyfikator dokumentu i źródło, daty i role osób, wynik detekcji (reguła/sygnał), skróty kryptograficzne plików przed/po, opis wykonanej sanitizacji oraz decyzję końcową. Dzięki temu w razie regresji (np. ponownego pojawienia się treści z prompt injection w odpowiedziach) możesz szybko wskazać, która wersja była indeksowana i dlaczego.
Jak zmniejszyć skutki ataku nawet wtedy, gdy złośliwy fragment przejdzie do indeksu?
Gdy złośliwa instrukcja trafi do indeksu RAG, nie chodzi już o „usunięcie problemu u źródła”, tylko o ograniczenie tego, czy i kiedy model w ogóle ją zobaczy oraz czy będzie mógł się do niej zastosować. Najważniejsze jest więc sterowanie powierzchnią ekspozycji: zawężanie wyszukiwania (retrieval) do minimalnie potrzebnego kontekstu i rygorystyczne separowanie poleceń systemowych od treści z dokumentów. Model powinien traktować kontekst z RAG jako dane, a nie jako instrukcje.
Praktycznie sprowadza się to do trzech warstw ograniczania szkód. Po pierwsze, kontrola retrieval: zmniejszanie liczby zwracanych fragmentów, wymuszanie dopasowania do intencji pytania (np. przez filtrowanie po metadanych, dziedzinie, typie sekcji) oraz blokowanie fragmentów o wysokim ryzyku (np. takich, które zawierają typowe zwroty „ignoruj poprzednie instrukcje”, „system prompt”, „wykonaj polecenie”). Dzięki temu nawet jeśli payload jest w indeksie, rzadziej zostanie pobrany do kontekstu odpowiedzi.
Po drugie, „bezpieczny prompt” i izolacja ról: w szablonie generowania należy jednoznacznie opisać, że cytowany kontekst to materiał referencyjny i nie może modyfikować reguł działania asystenta. Warto też wymuszać odpowiedzi oparte na cytatach/odniesieniach do źródeł, bo mechanizm „pokaż podstawę” utrudnia przemycanie poleceń jako rzekomych faktów i ułatwia wykrycie anomalii w logach.
Po trzecie, ograniczenie skutków wykonania: nawet jeśli model „uwierzy” w złośliwą instrukcję, nie powinien mieć możliwości realnego działania poza dozwolonym zakresem. Oznacza to twarde polityki dla narzędzi (tool use) i danych wrażliwych: minimalne uprawnienia, jawne allowlisty operacji, walidację parametrów wywołań oraz blokowanie ujawniania sekretów (np. kluczy API) niezależnie od tego, co znajduje się w kontekście. W efekcie atak może co najwyżej zniekształcić odpowiedź tekstową, ale nie doprowadzi do eksfiltracji ani wykonania niedozwolonych akcji.
Dodatkowo warto utrzymywać mechanizm reakcji po wykryciu incydentu: możliwość szybkiego „takedownu” fragmentów (usunięcie/wyłączenie dokumentu, reindeksacja), wersjonowanie indeksu i audyt tego, jakie fragmenty były pobierane przy danych zapytaniach. To nie zapobiega samemu przejściu payloadu do indeksu, ale skraca czas ekspozycji i pozwala precyzyjnie ocenić wpływ.