Human-in-the-loop, który kłamie — jak zaprojektować okno zgody, które naprawdę chroni

przez Łukasz | lip 11, 2026

Dodałeś potwierdzenie przez człowieka. To nie znaczy, że dodałeś zabezpieczenie.

Kiedy budujesz agenta, który robi coś nieodwracalnego — pisze pliki, wykonuje polecenia, wysyła pieniądze — prędzej czy później dokładasz punkt kontrolny: zanim agent to zrobi, pyta człowieka o zgodę. Człowiek w pętli. Wydaje się, że to zamyka temat bezpieczeństwa: cokolwiek agent wymyśli, na końcu jest ktoś, kto klika „tak” albo „nie”.

W lipcu 2026 badanie o nazwie GhostApproval pokazało, że to złudzenie — i to w sposób, który każdy builder powinien zrozumieć, zanim wdroży kolejnego agenta z „potwierdzeniem”. Sześć popularnych asystentów kodujących miało dokładnie taki punkt kontrolny: okno dialogowe, które pytało dewelopera o zgodę przed zapisem pliku. Wszystkie dało się oszukać. Nie przez ominięcie człowieka — przez pokazanie mu w oknie czegoś innego, niż agent faktycznie robił. Człowiek klikał „zatwierdź edycję project_settings.json„, a agent w tym czasie zapisywał klucz atakującego do ~/.ssh/authorized_keys.

Ten wpis nie jest o GhostApproval. Jest o tym, czego GhostApproval uczy każdego, kto sam projektuje punkt kontrolny w agencie: że okno zgody chroni dokładnie tyle, ile prawdy pokazuje — a domyślnie pokazuje jej mniej, niż myślisz.

Co się właściwie stało — od strony buildera

Mechanizm jest prosty i pouczający, bo nie ma w nim żadnej egzotyki. Jest za to trzy warstwy, które warto rozdzielić.

Atakujący przygotowuje repozytorium z dowiązaniem symbolicznym. Plik nazywa się project_settings.json, ale to symlink — wskaźnik prowadzący gdzie indziej, na ~/.ssh/authorized_keys. W README pisze: „dodaj tę linijkę do project_settings.json„. Deweloper klonuje repo i mówi agentowi: „skonfiguruj projekt według README”. Agent czyta instrukcję, otwiera project_settings.json do zapisu — a system operacyjny, podążając za dowiązaniem, kieruje ten zapis do pliku logowania SSH. Klucz atakującego ląduje tam, gdzie otwiera drzwi do maszyny.

Symlink to sztuczka sprzed dekad. Gdyby to było wszystko, byłby to zwykły błąd walidacji ścieżki. Ale sedno jest gdzie indziej — w oknie zgody. Bo te agenty miały punkt kontrolny. Zapis nie następował, dopóki deweloper nie kliknął „akceptuj”. Problem w tym, co okno pokazywało.

I tu jest szczegół, który powinien zmrozić każdego buildera: w testach agent w swoim własnym rozumowaniu poprawnie rozpoznał, dokąd naprawdę prowadzi zapis. Jeden z asystentów zanotował wprost, że plik jest „w rzeczywistości plikiem konfiguracyjnym zsh”. Agent wiedział. A okno pokazane człowiekowi i tak wyświetliło niewinną nazwę z instrukcji. Informacja, która pozwoliłaby podjąć decyzję, istniała — w głowie agenta — i nie trafiła do okna.

To jest różnica między nadzorem, który jest, a nadzorem, który działa. Punkt kontrolny formalnie istniał. Merytorycznie nie chronił, bo pokazywał nieprawdę.

Trzy antywzorce, które zamieniają twój punkt kontrolny w teatr

Jeśli budujesz agenta z potwierdzeniem przez człowieka, sprawdź, czy nie wpadłeś w któryś z nich. Wszystkie trzy pojawiły się w GhostApproval.

Antywzorzec 1: okno pokazuje intencję, nie skutek. Twój agent wyświetla to, o co poprosił użytkownik albo co mówi instrukcja („edytuj config.json„), zamiast tego, co faktycznie się wydarzy na dysku (zapis do /home/user/.ssh/authorized_keys). Intencja i skutek zwykle się pokrywają — i dlatego, gdy się rozjadą, nikt tego nie wychwytuje. Okno musi pokazywać rozwiązaną, końcową konsekwencję, nie deklarację.

Antywzorzec 2: ścieżka nie jest rozwiązana przed pokazaniem. Jeśli wyświetlasz użytkownikowi ścieżkę taką, jaką dostałeś (project_settings.json), zanim ją skanonikalizujesz — rozwiniesz symlinki, .., dowiązania — to pokazujesz etykietę, nie cel. Rozwiązanie ścieżki musi nastąpić przed renderowaniem okna, nie po kliknięciu „tak”.

Antywzorzec 3: zapis przed zgodą. Najgorszy z trzech, i realnie występujący: agent zapisuje zmianę na dysk, a dopiero potem pokazuje przycisk „akceptuj/odrzuć”. W jednym z sześciu narzędzi okno zgody było w praktyce przyciskiem „cofnij”, nie bramą — w chwili, gdy deweloper je widział, klucz atakującego był już w pliku. Jeśli twoja akcja wykonuje się przed potwierdzeniem, nie masz punktu kontrolnego. Masz log tego, co już się stało.

Jak wygląda okno, które chroni

Przełóżmy to na konkret. Oto ta sama operacja w dwóch wersjach — pozornej i realnej.

Wersja pozorna (antywzorce 1 i 2 naraz):

Agent chce zmodyfikować plik.
  Plik: project_settings.json
  [ Akceptuj ]  [ Odrzuć ]

Wygląda poprawnie. Deweloper widzi znajomą nazwę z README, klika „akceptuj”, a zapis idzie do ~/.ssh/authorized_keys. Okno zrobiło dokładnie to, co antywzorzec każe: pokazało etykietę zamiast celu.

Wersja, która chroni:

Agent chce zapisać plik.
  Poprosił o:      project_settings.json
  RZECZYWISTY cel: /home/user/.ssh/authorized_keys   ⚠ POZA PROJEKTEM
  Powód ostrzeżenia: to dowiązanie symboliczne prowadzi
                     poza katalog roboczy, do pliku logowania SSH
  [ Akceptuj ]  [ Odrzuć ]

Różnica nie jest kosmetyczna. Drugie okno rozwiązało ścieżkę przed pokazaniem, ujawniło rozjazd między tym, o co agent poprosił, a tym, co faktycznie zrobi, i oznaczyło zapis wychodzący poza obszar projektu jako wyróżniony, niebezpieczny. Deweloper, który zobaczy ~/.ssh/authorized_keys z ostrzeżeniem „poza projektem”, kliknie „odrzuć” — bo dostał informację, która pozwala podjąć decyzję. To jest cała różnica: pierwsze okno pyta o zgodę, drugie umożliwia świadomą zgodę.

W pseudokodzie logika, która stoi za drugim oknem, jest krótka:

python
def zapytaj_o_zapis(sciezka_zadana, workspace):
    # 1. ROZWIĄŻ ścieżkę zanim cokolwiek pokażesz
    cel = os.path.realpath(sciezka_zadana)   # rozwija symlinki i ..

    # 2. USTAL, czy cel wychodzi poza obszar projektu
    poza_projektem = not cel.startswith(os.path.realpath(workspace))

    # 3. POKAŻ skutek, nie intencję — z wyróżnieniem, gdy poza projektem
    okno = OknoZgody(
        poprosil_o = sciezka_zadana,
        rzeczywisty_cel = cel,
        ostrzezenie = poza_projektem,
    )

    # 4. NIE zapisuj, dopóki nie ma zgody — brama, nie „cofnij"
    if not okno.zaakceptowane():
        return  # nic nie zostało zapisane
    wykonaj_zapis(cel)

Cztery kroki, a każdy zamyka jeden antywzorzec: realpath przed pokazaniem (2), porównanie z workspace (skutek, nie intencja), wyróżnienie zapisu poza projektem (1), i zapis dopiero po zgodzie (3). To nie jest dużo kodu. Brak każdego z tych kroków to jednak różnica między nadzorem realnym a pozornym.

Głębsza zasada: parytet informacji, nie obecność człowieka

Łatwo z tego wyciągnąć wniosek „trzeba lepiej rozwiązywać ścieżki” i na tym poprzestać. To za mało. GhostApproval akurat użył symlinka, ale zasada jest ogólniejsza i dotyczy każdego punktu kontrolnego, jaki zbudujesz.

Punkt kontrolny człowieka chroni wtedy i tylko wtedy, gdy człowiek widzi to samo, co wie agent w momencie decyzji. Jeśli agent zna skutek, którego nie pokazuje — bo pokazuje intencję, bo nie rozwiązał ścieżki, bo streścił dziesięć operacji jako „konfiguracja projektu” — to pętla jest rozerwana, choć formalnie istnieje. Nazwijmy tę własność wprost: parytet informacji. To ona jest tym, co próbujesz zagwarantować, a nie sama obecność kliknięcia.

To dotyczy nie tylko ścieżek plików. Ten sam problem masz, gdy agent:

  • streszcza w oknie zgody serię akcji jako jedną („wykonać skrypt konfiguracyjny?”), ukrywając, że jedna z nich wysyła dane na zewnątrz,
  • pokazuje nazwę narzędzia, ale nie argumenty, z jakimi je wywoła („wywołać send_email?” bez pokazania adresata i treści),
  • prosi o zgodę na „zapytanie do bazy”, nie pokazując, że to DROP TABLE.

Za każdym razem mechanizm jest ten sam co w GhostApproval: agent wie więcej, niż pokazuje człowiekowi w punkcie decyzji. I za każdym razem lekarstwo jest to samo — pokaż w oknie rozwiązany, konkretny skutek operacji, nie jej nazwę ani intencję. To jest praktyczna treść zasady, którą słownik nazywa różnicą między człowiekiem w pętli a uprawnieniami agenta: uprawnienia mówią, co agent może zrobić technicznie; punkt kontrolny mówi, kiedy ma zapytać — ale pytanie jest warte tyle, ile prawdy niesie.

Checklista: audyt twojego punktu kontrolnego

Przejdź przez to dla każdego miejsca w agencie, gdzie prosisz człowieka o zgodę na nieodwracalną akcję.

  • Pokazujesz skutek, czy intencję? W oknie ma być to, co faktycznie się wydarzy (rozwiązana ścieżka, konkretne argumenty, realne zapytanie), nie to, o co poproszono.
  • Rozwiązujesz ścieżki i odwołania przed renderowaniem okna? realpath na plikach, pełny adresat i treść przy mailu, rozwinięte zapytanie przy bazie — wszystko przed pokazaniem, nie po kliknięciu.
  • Oznaczasz akcje wychodzące poza oczekiwany obszar? Zapis poza katalogiem roboczym, wywołanie do zewnętrznego hosta, operacja na zasobie spoza projektu — to musi wyglądać w oknie inaczej niż akcja rutynowa.
  • Twoje okno to brama czy „cofnij”? Sprawdź w kodzie, czy akcja wykonuje się dopiero po zgodzie. Jeśli cokolwiek zapisuje się przed potwierdzeniem, nie masz punktu kontrolnego.
  • Czy człowiek widzi to, co „wie” agent? Jeśli agent w swoim rozumowaniu ma informację istotną dla decyzji (że plik jest wrażliwy, że host jest zewnętrzny), a okno jej nie pokazuje — domknij tę lukę. To jest parytet informacji.
  • Czy zgoda dotyczy pojedynczej akcji, czy zbiorczej? „Wykonaj wszystkie kroki” ukrywa ten jeden groźny. Rozbijaj zgodę na tyle akcji, ile trzeba, by każda groźna była widoczna osobno.

Na koniec

Człowiek w pętli nie jest magicznym zabezpieczeniem, które dokładasz na końcu i przestajesz się martwić. Jest interfejsem — a interfejs chroni tylko tyle, ile prawdy pokazuje. GhostApproval nie złamał żadnego szyfru ani nie znalazł błędu w modelu. Wykorzystał to, że okno zgody, w które wszyscy wierzyliśmy, pokazywało ładną etykietę zamiast prawdziwego celu. Twój agent będzie miał ten sam problem domyślnie — bo pokazywanie intencji zamiast skutku jest łatwiejsze, a rozwiązywanie ścieżek przed renderowaniem okna trzeba świadomie zaprogramować. Zabezpieczeniem nie jest obecność pytania. Jest to, że człowiek, którego pytasz, widzi dokładnie to, co za chwilę zrobi maszyna.


Ten wpis należy do serii Anatomia agenta AI i rozwija wątek z artykułu „Co może pójść nie tak — bezpieczeństwo agentów dla builderów”. Pojęcia człowiek w pętli i uprawnienia agenta znajdziesz w Słowniku Agentic Web. Szczegółową analizę incydentu GhostApproval po stronie bezpieczeństwa publikuje cyberflux.pl.

Źródła: Wiz — GhostApproval · The Hacker News · The Register

Oznaczanie treści generowanych przez AI — kto, co i od kiedy

Oznaczanie treści generowanych przez AI — kto, co i od kiedy

To mapa regulacji, nie porada prawna. Opisujemy konstrukcję obowiązków z art. 50 AI Act według tekstu przepisów i publicznie dostępnych projektów wytycznych. Czy i jak dotyczą Twoich treści — rozstrzyga prawnik. Data ostatniej weryfikacji treści: lipiec 2026. AI Act...

AI Website Tycoon

👑 AI WEBSITE TYCOON Symulator agencji stron przyszłości Rok 2025 Budżet 100 000 zł ⛶ 1 · Klient 2 · Budżet 3 · Wydarzenia 4 · Wyniki 5 · Raport AI Zbuduj stronę, którą poleci AI — nie tylko taką, która ładnie wygląda. Dostajesz klienta i 100 000 zł. Twoje...

AI Overviews i Tryb AI — warstwa AI w samym Google

AI Overviews i Tryb AI — warstwa AI w samym Google

Google wbudował AI w wyszukiwarkę na dwóch poziomach: AI Overviews to generowane przez model podsumowanie z cytowaniami, pojawiające się nad klasycznymi wynikami przy części zapytań, a Tryb AI (AI Mode) to osobna, w pełni konwersacyjna warstwa wyszukiwarki — dostępna...