SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor

Jak chronić sekrety przed agentami AI

Klucz API w zmiennej środowiskowej może zostać wykradziony jednym wywołaniem narzędzia. Stosuje się krótkotrwałe tokeny o ograniczonym zakresie i bramę poświadczeń.

Co oznacza utrzymywanie sekretów poza agentami AI

Agent AI to standardowy proces systemu Linux wykonujący polecenia. Każda zmienna środowiskowa przypisana do tego procesu jest odczytywalna przez uruchamiany przez niego kod, więc klucz API w środowisku agenta jest kluczem, który agent może wysłać do dowolnego dostępnego hosta. Utrzymywanie sekretów poza agentem oznacza przekazanie mu uchwytu zamiast klucza: krótkotrwałego tokena o ograniczonym zakresie lub symbolu zastępczego, który jest wymieniany na rzeczywistą wartość na granicy sieci przez inny komponent.

Nie jest to historia o modelu stającym się wrogim. Mechanizm jest bardziej prozaiczny. Agent odczytuje stronę internetową, plik README lub komentarz w zgłoszeniu zawierający instrukcje i wykonuje je, ponieważ dla modelu językowego nie ma różnicy między tekstem napisanym przez użytkownika a tekstem pobranym z zewnątrz. To jest wstrzykiwanie promptu (prompt injection). Gdy do tego dojdzie, zakres szkód jest ograniczony dokładnie przez jedną rzecz: to, co proces może odczytać. Jeśli granice nie zostały jeszcze wyznaczone, bezpieczne uruchamianie agenta programistycznego na serwerze opisuje poziomy izolacji, na których opiera się ten przewodnik.

Model zagrożeń w prostych słowach

Uruchom to jako użytkownik, na którego koncie działa Twój agent.

tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'

Każda linia, którą wypisuje, znajduje się w odległości jednego żądania HTTP od serwera nieznanej osoby. Teraz spójrz, co znajduje się na dysku w pobliżu agenta.

grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'

Agent z dostępem do powłoki nie potrzebuje wyrafinowanego exploita, aby wyprowadzić te dane. Cztery zwykłe ścieżki wykonują to zadanie, a wszystkie cztery wyglądają w logach jak normalna praca:

  • Wychodzące połączenie curl lub fetch do dowolnego hosta, z wartością umieszczoną w ciągu zapytania (query string).
  • git commit oraz git push do repozytorium, do którego agent ma uprawnienia zapisu.
  • Skrypt instalacyjny pakietu, który uruchamia dowolny kod jako użytkownik agenta.
  • Zapytanie DNS o nazwę hosta zawierającą tę wartość, co działa nawet wtedy, gdy ruch wychodzący HTTP jest zablokowany.

Nie da się rozwiązać tego problemu poprzez samą weryfikację kodu. Rozwiązaniem jest upewnienie się, że w zasięgu agenta nie znajduje się nic wartościowego.

Tajny plik w drzewie roboczym znajduje się w oknie kontekstu

Agent odczytuje pliki. Plik .env znajdujący się w repozytorium, w którym agent pracuje, zostanie odczytany. Po odczytaniu znajduje się już w oknie kontekstu, czyli w transkrypcji, w każdym przechowywanym dzienniku oraz we wszystkim, co agent zapisze później.

Wcześniej, gdy klucz znajdował się w drzewie, w którym pracuje agent:

cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billing

Później, gdy plik został przeniesiony poza jego zasięg:

sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.env

Użytkownik agenta nie może już otworzyć tego pliku, ponieważ drzewo robocze go nie zawiera. Reguły deny w konfiguracji samego agenta stanowią drugą warstwę, a nie pierwszą. Claude Code odczytuje reguły uprawnień z .claude/settings.json w projekcie:

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

Zapobiega to przypadkowemu otwarciu pliku przez agenta podczas przeglądania projektu. Reguły działają również poza narzędziami do obsługi plików: Claude Code sprawdza je także dla rozpoznawanych poleceń plikowych w Bash, takich jak cat i head, oraz dla celów przekierowań, takich jak < .env. Nie uniemożliwia to wykonania przez wstrzykniętą instrukcję polecenia grep -r KEY . w katalogu projektu ani krótkiego skryptu Python, który samodzielnie otwiera .env, ponieważ dokumentacja uprawnień wskazuje, że reguły deny nie dotyczą poleceń odczytujących pliki bez podania ich nazw ani procesów potomnych, które samodzielnie otwierają pliki. W tych przypadkach dokumentacja wskazuje sandbox Claude Code, który wymusza blokadę na poziomie systemu operacyjnego. To, czy przed wykonaniem takiego polecenia zostanie wyświetlone pytanie, zależy od trybu uprawnień sesji. Tryb automatyczny jest obecnie wbudowanym trybem początkowym interaktywnych sesji Claude Code, dlatego serwer, którego działanie nie jest monitorowane, wykona więcej takich poleceń bez pytania. To samo ograniczenie dotyczy wszystkiego, co kształtuje sposób działania agenta, a nie jego uprawnienia: umiejętność wymagająca od agenta wprowadzania najmniejszej skutecznej zmiany ogranicza dostęp do plików, których agent nie powinien otwierać, ale nadal jest tylko wskazówką, którą można podważyć w toku rozmowy z modelem. Konfigurację należy traktować jako zabezpieczenie, a uprawnienia systemu plików jako barierę. Ten sam podział występuje w kontenerach: pliki env i sekrety w Docker Compose opisują wariant tego problemu występujący o jedną warstwę niżej.

Nadaj każdemu agentowi osobnego użytkownika bez uprawnień

Jeśli agent działa na Twoim koncie, dziedziczy Twoje klucze SSH, poświadczenia chmurowe oraz historię powłoki. Osobny użytkownik to koszt jednego polecenia, który eliminuje wszystkie te zagrożenia.

sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519

Ostatnia linia musi zakończyć się niepowodzeniem z użyciem cat: /home/you/.ssh/id_ed25519: Permission denied. Jeśli wyświetli klucz, użytkownik agenta ma dostęp do pliku. Otwarcie pliku po nazwie wymaga uprawnienia do przeszukiwania (x) każdego katalogu nadrzędnego, a nie uprawnienia do odczytu. Dlatego katalog domowy z uprawnieniami 711, którego nikt inny nie może wyświetlić, nadal pozwala na dostęp do klucza, jeśli sam plik klucza jest czytelny. chmod 700 ~ zamyka ścieżkę, a chmod 600 ~/.ssh/id_ed25519 naprawia klucz. Nie dodawaj użytkownika agenta do sudo i nie nadawaj mu reguły NOPASSWD szerszej niż to jedno polecenie, którego faktycznie potrzebuje. Użytkownicy z minimalnymi uprawnieniami na VPS omawia szczegółowo grupy i plik sudoers. Pamiętaj o tej separacji, gdy uruchamiasz więcej niż jedną sesję na serwerze, ponieważ jedna sesja Claude Code może wysłać tekst bezpośrednio do drugiej, a wszystko, do czego ma dostęp pierwsza sesja, może zostać przesłane tym kanałem w pojedynczej wiadomości.

Na chmurowym serwerze VPS warto dodać jeszcze jedną granicę. Usługa metadanych instancji odpowiada pod stałym adresem lokalnym i często przekazuje poświadczenia roli każdemu, kto o nie zapyta. Punkt końcowy nie sprawdza, który użytkownik lokalny wysyła zapytanie: w przypadku EC2, AWS dokumentuje, że wywołania IMDSv1 nie wymagają tokena, a ich własna porada dotycząca odizolowania użytkownika od usługi polega na zastosowaniu lokalnej reguły firewalla opartej na identyfikatorze użytkownika.

sudo iptables -I OUTPUT 1 -p tcp -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECT

Wstaw regułę na początku za pomocą -I, zamiast dodawać ją na końcu za pomocą -A. Na hoście z uruchomionym ufw z domyślną polityką zezwalającą na ruch wychodzący, ostatni łańcuch OUTPUT w ufw akceptuje każde nowe połączenie TCP, więc reguła dodana na końcu nigdy nie zostanie osiągnięta. Reguły iptables znikają również po restarcie, chyba że zostaną utrwalone. Jeśli włączono punkt końcowy IPv6 dla usługi metadanych, [fd00:ec2::254], dodaj tę samą regułę za pomocą ip6tables.

Sprawdź to z poziomu agenta. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ powinno nie wyświetlić nic i zakończyć się kodem błędu, ponieważ pakiet jest odrzucany, zanim opuści serwer. Jeśli wyświetli listę, reguła nie jest osiągana.

Wstrzykiwanie danych uwierzytelniających na granicy

Wzorzec, który faktycznie rozwiązuje ten problem, polega na wstrzykiwaniu danych uwierzytelniających. Agent nigdy nie przechowuje prawdziwego klucza. Wysyła żądanie przez lokalną bramę, a brama po drodze zastępuje wartość zastępczą prawdziwym sekretem. Sekret jest przechowywany w magazynie bramy, w innym procesie, uruchomionym przez innego użytkownika.

OneCLI to jedna z implementacji open source tego rozwiązania. Jest objęty licencją Apache-2.0, z wyjątkiem katalogów ee/, które są objęte odrębną licencją enterprise wymagającą subskrypcji w środowisku produkcyjnym. OneCLI działa w kontenerach uruchomionych obok agenta. W październiku 2026 projekt opisuje tę konfigurację Docker Compose, która odczytuje trzy wygenerowane sekrety z pliku .env znajdującego się obok pliku Compose:

git clone https://github.com/onecli/onecli.git && cd onecli/docker
cat > .env <<EOF
SECRET_ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
GATEWAY_INTERNAL_SECRET=$(head -c 32 /dev/urandom | base64)
BETTER_AUTH_SECRET=$(head -c 32 /dev/urandom | base64)
COMPOSE_PROFILES=runner
EOF
chmod 600 .env
docker compose up -d --wait

Wykonaj kopię zapasową pliku .env, ponieważ utrata SECRET_ENCRYPTION_KEY powoduje utratę dostępu do każdego przechowywanego w nim sekretu. Panel nasłuchuje na porcie 10254, a brama na porcie 10255. Domyślnie oba są powiązane z adresem 127.0.0.1. Rzeczywiste dane uwierzytelniające są przechowywane tylko raz. Następnie każdemu agentowi nadaje się wartość zastępczą zamiast klucza oraz własny token dostępu o ograniczonym zakresie. Agent wysyła token w nagłówku Proxy-Authorization. Brama identyfikuje żądanie wychodzące na podstawie hosta i ścieżki, odszyfrowuje pasujące dane uwierzytelniające i podstawia je w żądaniu. Środowisko agenta nie zawiera niczego, co byłoby przydatne osobie atakującej.

Wartość tego rozwiązania nie polega na samym szyfrowaniu. Chodzi o to, że pytanie „czego użył ten agent i kiedy” staje się zapytaniem do dziennika. Zamiast zgadywać, w którym z sześciu środowisk znajdowała się kopia klucza, można odczytać jeden dziennik audytowy.

Przekazuj sekret do procesu, a nie do środowiska

Jeśli agent działa pod kontrolą systemd, zmienne środowiskowe nie są w ogóle potrzebne. LoadCredential= umieszcza sekret w prywatnym katalogu, który może odczytać tylko użytkownik usługi. W pliku jednostki katalog jest udostępniany jako %d, a wewnątrz procesu jako $CREDENTIALS_DIRECTORY. Ograniczenie dotyczy użytkownika, a nie usługi. Dlatego każdej usłudze agenta należy przypisać osobnego użytkownika. Każdy inny proces działający z tym samym użytkownikiem może otworzyć ten katalog, chyba że jednostka używa również przestrzeni nazw systemu plików, na przykład PrivateMounts=yes, która ukrywa katalog przed innymi usługami. Wartość nigdy nie pojawia się w /proc/<pid>/environ, więc ps eww nie może jej wyświetlić. Katalog znika po zatrzymaniu usługi.

Najpierw zaszyfruj dane uwierzytelniające dla danego komputera. Te polecenia pochodzą z dokumentacji systemd i działają w systemd 250 lub nowszym, co obejmuje Ubuntu 24.04 i Debian 13:

echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
  systemd-creds cat api_key

Ostatnie polecenie wyświetla sk-example-value. Potwierdza to, że zaszyfrowany plik można odszyfrować na tym hoście. Następnie odwołaj się do niego w jednostce:

[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-worker

Kod agenta otwiera plik pod adresem $AGENT_KEY_FILE, gdy potrzebuje tej wartości. Odczyt pliku jest chwilową operacją. Zmienna środowiskowa pozostaje dostępna przez cały czas działania procesu oraz w każdym uruchomionym przez niego procesie potomnym.

Preferuj tokeny krótkotrwałe zamiast kluczy długoterminowych

Klucz, który nigdy nie wygasa, pozostaje ważny w każdym momencie, gdy po miesiącach pojawi się w logach lub zapisie sesji. Jeśli usługa oferuje token sesji, należy go użyć i ustawić najkrótszy czas życia, na jaki pozwala wykonywane zadanie.

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/agent-readonly \
  --role-session-name agent-review \
  --duration-seconds 900

Piętnaście minut to minimalny czas akceptowany przez AWS STS (security token service), co zazwyczaj wystarcza na wykonanie jednego zadania przez agenta. W przypadku GitHub należy utworzyć dla użytkownika-agenta osobny login gh z tokenem o ograniczonym zakresie, przypisanym tylko do jednego repozytorium, w którym agent pracuje. Dzięki temu gh auth token w ramach tej sesji zwróci wynik, który nie pozwoli na dostęp do żadnych innych zasobów. Zakres uprawnień należy ograniczać najpierw według zasobów, a następnie według czasu.

Zweryfikuj, a następnie weryfikuj ponownie

Po każdej zmianie konfiguracji agenta warto wykonać trzy testy. Należy uruchamiać je jako użytkownik agenta, a nie jako bieżący użytkownik.

sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/user

Pierwsze polecenie nie powinno wyświetlić niczego. Drugie powinno wyświetlić ls: cannot open directory '/home/you/': Permission denied, ale oznacza to tylko, że nie można wyświetlić zawartości katalogu; dopiero wcześniejszy test cat potwierdza, że nie można odczytać znajdującego się w nim pliku. Trzecie polecenie pokazuje, z jaką tożsamością występuje ścieżka sieciowa agenta. Jest to informacja, którą ma zapewnić wzorzec bramy: 401 oznacza, że żądanie dotarło do GitHub bez poświadczenia akceptowanego przez GitHub — bez żadnego poświadczenia albo z poświadczeniem nieprawidłowym lub wygasłym. 200 oznacza, że żądanie zawiera prawidłowe poświadczenie, dlatego należy wiedzieć, którego tokenu używa agent. W przypadku agentów uruchamianych bez nadzoru temat kontrolowanie kosztów agentów AI na VPS omawia limity budżetowe, które uzupełniają te ograniczenia dostępu.

FAQ

Czy mogę po prostu zaufać modelowi, że nie ujawni moich kluczy?

Nie, ponieważ w tym modelu zagrożeń to nie model jest atakującym. Agent odczytuje tekst ze stron internetowych, repozytoriów i systemów śledzenia zgłoszeń, a tekst ten może zawierać instrukcje. Model nie posiada niezawodnego sposobu na odróżnienie Twoich instrukcji od pobranego tekstu. Każde zabezpieczenie polegające na poprawnym wyborze modelu zawiedzie, gdy tylko wstrzyknięta instrukcja okaże się przekonująca. Dlatego mechanizm kontrolny musi znajdować się w systemie operacyjnym lub w sieci.

Czy zmienne środowiskowe są rzeczywiście tak złe dla sekretów agenta?

Są one szkodliwe w jednym konkretnym aspekcie: są dziedziczone. Każdy proces potomny uruchamiany przez agenta otrzymuje ich kopię, w tym skrypty budowania, narzędzia testowe oraz wszelkie skrypty instalacyjne pakietów. Zmienne te są również odczytywalne przez /proc/<pid>/environ przez tego samego użytkownika, więc każdy proces uruchomiony przez agenta może je odczytać bez konieczności przekazywania ich przez agenta. Odczyt pliku w momencie użycia, za pomocą LoadCredential= lub bramy, ogranicza ekspozycję tylko do tego konkretnego momentu.

Czy umieszczenie sekretów w skarbcu (vault) rozwiązuje ten problem samodzielnie?

Tylko częściowo. Skarbiec rozwiązuje kwestię przechowywania. Jeśli hostujesz go samodzielnie, wymaga on własnego procesu utwardzania, ponieważ serwer Vaultwarden jest zazwyczaj przełamywany przez token administratora lub plik kopii zapasowej, a nie przez zaszyfrowane elementy, które zawiera. Nie rozwiązuje to ostatniego etapu, w którym proces pobiera sekret ze skarbca i przekazuje go agentowi jako zmienną środowiskową, co przywraca stan wyjściowy. Kluczowe jest to, kto wykonuje podstawienie. Jeśli sekret pobiera agent, to agent go posiada. Jeśli podstawienie wykonuje brama lub system init poza procesem agenta, agent nigdy nie wchodzi w jego posiadanie.

Skąd mam wiedzieć, czy agent już coś ujawnił?

Zazwyczaj nie da się tego stwierdzić po fakcie, co stanowi argument za stosowaniem bramy. Bez niej dowody są rozproszone w historii powłoki, transkrypcjach agenta oraz logach połączeń wychodzących, których prawdopodobnie nie przechowujesz. Dzięki bramie poświadczeń każde użycie danych uwierzytelniających to jedna linia z identyfikatorem agenta i znacznikiem czasu. Jeśli podejrzewasz wyciek, najpierw zrotuj klucz, a dopiero potem prowadź dochodzenie. Rotacja jest tania, a pewność nie.

Co powinienem zrobić dzisiaj w pierwszej kolejności?

Przenieś każdy plik .env poza katalogi, w których pracują Twoi agenci, i utwórz jednego użytkownika bez uprawnień dla każdego agenta. Te dwie zmiany zajmują około dziesięciu minut i zamykają najczęstszą ścieżkę ataku, czyli odczyt przez agenta pliku z poświadczeniami, który nie powinien znajdować się obok kodu. Brama i krótkotrwałe tokeny to kolejny krok, a nie pierwszy. Ten sam punkt wyjścia dotyczy każdego środowiska uruchomieniowego agenta, w tym bezpiecznego uruchamiania autonomicznego agenta na VPS.

Czy ten artykuł był przydatny?