Як не передавати секрети AI-агентам
API key у змінній середовища може витекти за один виклик tool. Використовуйте короткоживучі токени з обмеженими правами через credential gateway.
Що означає не передавати секрети AI-агентам
AI-агент — це звичайний процес Linux, який виконує команди. Код, що виконується цим процесом, може читати кожну змінну середовища, яку він містить. Тому API key у середовищі агента може бути надісланий агентом на будь-який доступний йому хост. Не передавати секрети агенту означає надати йому дескриптор замість ключа: короткостроковий токен з обмеженою областю дії або заповнювач, який інший компонент замінює реальним значенням на межі мережі.
Це не історія про те, що модель стала ворожою. Механізм простіший. Агент читає вебсторінку, README або коментар до issue з інструкціями й виконує їх, оскільки для мовної моделі немає різниці між текстом, який написали ви, і текстом, який вона отримала з мережі. Це prompt injection. Після цього масштаб шкоди визначає лише одне: що саме може читати процес. Якщо ви ще не встановили межу, у матеріалі безпечний запуск coding agent на сервері описано рівні ізоляції, на яких ґрунтується цей посібник.
Модель загроз простими словами
Запускайте це від імені користувача, від якого працює ваш агент.
tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'Кожен надрукований ним рядок — це один HTTP-запит до сервера сторонньої особи. Тепер перевірте, що зберігається на диску поруч з агентом.
grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'Агенту з доступом до shell не потрібен складний експлойт, щоб вивести ці дані. Для цього достатньо чотирьох звичайних способів, і в журналі всі вони виглядають як стандартна робота:
- Вихідний
curlабоfetchдо будь-якого вузла, де значення передається в рядку запиту. - Виконання
git commitіgit pushу репозиторії, до якого агент має доступ на запис. - Скрипт встановлення пакета, який виконує довільний код від імені користувача агента.
- DNS-запит для імені хоста, що містить це значення. Він виконується навіть за заблокованого вихідного HTTP-трафіку.
Цю проблему неможливо усунути лише перевіркою. Потрібно забезпечити, щоб у зоні доступу не було цінних даних.
Секрет у робочому дереві є секретом у контекстному вікні
Агент читає файли. Файл .env у репозиторії, з яким він працює, буде прочитано. Після цього він потрапить у контекстне вікно, тобто до transcript, будь-якого журналу, який ви ведете, і всього, що агент напише далі.
Раніше ключ знаходився в дереві, з яким працює агент:
cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billingТепер файл переміщено за межі доступу:
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Користувач агента більше не може відкрити файл, оскільки робоче дерево його більше не містить. Правила заборони у власній конфігурації агента є другим рівнем захисту, а не першим. Claude Code читає правила доступу з .claude/settings.json у проекті:
{
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
}
}Це запобігає ненавмисному відкриттю файлу агентом під час дослідження. Але це не запобігає виконанню ін’єкційної інструкції через base64 .env, оскільки це команда оболонки, а не читання файлу. Розглядайте конфігурацію як захисне обмеження, а дозволи файлової системи — як бар’єр. Такий самий поділ діє всередині контейнерів: у матеріалі файли середовища та секрети в Docker Compose розглянуто цю проблему на один рівень нижче.
Надайте кожному агенту окремого непривілейованого користувача
Якщо агент працює від вашого імені, він отримує доступ до ваших SSH-ключів, облікових даних хмарних сервісів і історії shell. Окремий користувач потребує виконання однієї команди та усуває все це.
sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519Останній рядок має завершитися помилкою з cat: /home/you/.ssh/id_ed25519: Permission denied. Якщо замість цього він виводить ключ, ваш домашній каталог доступний для читання групі або всім користувачам, а chmod 700 ~ виправляє це. Не додавайте користувача агента до sudo і не надавайте йому правило NOPASSWD, ширше за єдину команду, яка йому справді потрібна. Користувачі з мінімальними привілеями на VPS докладно описує налаштування груп і sudoers.
На хмарному VPS також варто додати ще один рівень ізоляції. Служба метаданих екземпляра відповідає на фіксованій link-local адресі та часто надає облікові дані ролі будь-якому клієнту, який їх запитує.
sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTПеревірте це з боку агента. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ має нічого не вивести та завершитися з ненульовим кодом, оскільки пакет відхиляється до того, як залишає сервер.
Вставляйте облікові дані на межі
Цю проблему вирішує ін’єкція облікових даних. Агент ніколи не зберігає справжній ключ. Він надсилає запит через локальний шлюз, а шлюз під час передавання замінює заповнювач на справжній секрет. Секрет зберігається у сховищі шлюзу, в іншому процесі та належить іншому користувачу.
OneCLI — одна з реалізацій цього підходу з відкритим вихідним кодом за ліцензією Apache-2.0. Вона працює як контейнер поруч з агентом. Станом на July 2026 проєкт описує таку конфігурацію:
git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --waitПанель керування прослуховує порт 10254, а шлюз — порт 10255. Ви одноразово зберігаєте справжні облікові дані, а кожному агенту надаєте значення-заповнювач замість ключа та окремий токен доступу з обмеженою областю дії. Агент надсилає цей токен у заголовку Proxy-Authorization. Шлюз визначає відповідний вихідний запит за хостом і шляхом, розшифровує відповідні облікові дані та підставляє їх. У середовищі агента не зберігається нічого, що становить цінність для зловмисника.
Перевага цього підходу не в шифруванні. Вона в тому, що запит «що використав цей агент і коли» перетворюється на запит до журналу. Ви переглядаєте один журнал аудиту замість того, щоб визначати, у якому з шести середовищ зберігалася копія ключа.
Передавайте секрет процесу, а не середовищу
Якщо ви запускаєте агент через systemd, змінні середовища взагалі не потрібні. LoadCredential= розміщує секрет у приватному каталозі, який може читати лише ця служба. У файлі модуля він доступний як %d, а всередині процесу — як $CREDENTIALS_DIRECTORY. Значення ніколи не з’являється в /proc/<pid>/environ, тому ps eww не може його показати. Каталог видаляється після зупинки служби.
Спочатку зашифруйте облікові дані для цього комп’ютера. Ці команди взято з документації systemd. Вони працюють у systemd 250 або новішої версії, зокрема в Ubuntu 24.04 і 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Остання команда виводить sk-example-value. Це підтверджує, що зашифрований файл розшифровується на цьому хості. Потім додайте посилання на нього в модулі:
[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Код агента відкриває файл за шляхом $AGENT_KEY_FILE, коли йому потрібне це значення. Читання файлу триває лише мить. Змінна середовища зберігається протягом усього часу роботи процесу та доступна кожному дочірньому процесу, який він запускає.
Надавайте перевагу короткоживучим токенам, а не довгоживучим ключам
Ключ, який ніколи не втрачає чинність, залишається дійсним щоразу, коли через місяці з’являється в журналі або транскрипті. Якщо сервіс підтримує токен сеансу, використовуйте його та встановлюйте найкоротший термін дії, який допускає завдання.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 900П’ятнадцять хвилин — мінімальний термін, який приймає AWS STS (security token service). Зазвичай цього достатньо для одного завдання агента. Для GitHub створіть для користувача агента окремий обліковий запис gh із токеном із деталізованими дозволами, обмеженим одним репозиторієм, з яким він працює. Тоді gh auth token у межах цього сеансу поверне об’єкт, який не може взаємодіяти з будь-чим іншим. Спочатку обмежуйте доступ ресурсом, а потім часом.
Перевіряйте, а потім продовжуйте перевіряти
Після будь-якої зміни конфігурації агента варто виконати три перевірки. Виконуйте їх від імені користувача агента, а не від свого імені.
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Перша команда взагалі не повинна вивести жодного тексту. Друга має вивести ls: cannot open directory '/home/you/': Permission denied. Третя показує, яку ідентичність представляє мережевий шлях агента. Саме на це має відповісти шаблон шлюзу: 401 означає, що агент не передає власні облікові дані GitHub, а 200 означає, що передає. У такому разі потрібно знати, який саме токен він використовує. Якщо ви запускаєте агентів без нагляду, у матеріалі контроль витрат на AI-агентів на VPS описано бюджетні обмеження, які доповнюють ці обмеження доступу.
FAQ
Чи можна просто довіритися моделі й не побоюватися витоку моїх ключів?
Ні, оскільки в цій моделі загроз атакує не сама модель. Агент читає текст вебсторінок, репозиторіїв і трекерів проблем, а цей текст може містити інструкції. Модель не має надійного способу відрізнити ваші інструкції від отриманого тексту. Будь-який механізм контролю, що залежить від правильного вибору моделі, не спрацює, щойно ін’єктована інструкція виявиться переконливою. Тому такий контроль має реалізовуватися в операційній системі або мережі.
Невже змінні середовища справді настільки небезпечні для секретів агента?
Вони небезпечні з однієї конкретної причини: вони успадковуються. Кожен дочірній процес, який запускає агент, отримує їхню копію, зокрема build script, test runner і будь-який package install hook. Змінні також можна прочитати через /proc/<pid>/environ від імені того самого користувача, тому все, що запускає агент, може прочитати їх без передавання з боку агента. Файл, який читається в момент використання, через LoadCredential= або gateway, обмежує доступ до цього моменту.
Чи вирішує розміщення секретів у vault цю проблему само по собі?
Лише частково. Vault вирішує проблему зберігання. Він не вирішує проблему останнього кроку, коли щось отримує секрет із vault і передає його агенту як змінну середовища. У такому разі ви повертаєтеся до початкової проблеми. Важливо, хто виконує підстановку. Якщо секрет отримує агент, секрет опиняється в агента. Якщо підстановку виконує gateway або init system поза процесом агента, агент ніколи не отримує секрет.
Як дізнатися, чи агент уже щось витік?
Зазвичай постфактум це неможливо визначити, і саме це є аргументом на користь gateway. Без gateway докази розпорошені між історією shell, transcript агента та журналами вихідних з’єднань, які ви, найімовірніше, не зберігаєте. Із credential gateway кожне використання облікових даних фіксується одним рядком із ідентифікатором агента та часовою позначкою. Якщо ви підозрюєте витік, спочатку змініть ключ, а потім проводьте розслідування. Ротація коштує дешево, а впевненість — ні.
Що мінімально потрібно зробити сьогодні?
Перемістіть кожен файл .env за межі каталогів, у яких працюють ваші агенти, і створіть окремого непривілейованого користувача для кожного агента. Ці дві зміни займають приблизно десять хвилин і усувають найпоширеніший шлях витоку: агент читає файл облікових даних, якому не було підстав знаходитися поруч із кодом. Наступним кроком мають бути gateway і короткоживучі токени, а не першим. Такий самий початковий підхід застосовується до будь-якого середовища виконання агента, зокрема безпечного запуску автономного агента на VPS.