Как пароль FTP утёк в бесплатную модель ИИ: случай aredel
Кратко
22.10.2026, пока проверялись deny-правила репозитория aredel, поиск по локальной истории OpenCode показал, что пароль учётной записи FTP для деплоя дважды попадал в модель в сентябре: один раз его вставили в чат вручную, другой раз его напечатал агент, выполнивший cat файла с учётными данными. Обе сессии шли на бесплатном тарифе для участников Muse Spark 1.3, который может использовать запросы и ответы для обучения будущих моделей. Отозвать это невозможно, поэтому пароль сменили на хостинге в тот же день.
Эта страница — разбор: что случилось, почему, как это нашли, не печатая секрет снова, и что изменилось. Пароля, его длины и имени учётной записи здесь нет.
Хронология
| Когда (UTC) | Что случилось |
|---|---|
| 2026-09-22 08:33 | Агент пишет скрипт деплоя и шаблон учётных данных. Шаблон говорит скопировать его в ~/.config/aredel/ftp-creds.env, вне репозитория, и заполнить. |
| 2026-09-22 09:15 | Заполненный файл вместе с паролем вставляют в тот же чат сообщением пользователя. |
| 2026-09-29 18:42 | В другой сессии агент выполняет cat ~/.config/aredel/ftp-creds.env. Вывод уходит в модель, и её ответ повторяет файл. Проектной конфигурации ещё не было, поэтому оболочка работала без запроса. |
| 2026-09-30 05:13–05:40 | Агент загружает файл через . ~/.config/aredel/ftp-creds.env в четырёх тестовых FTP-командах. Значения используются, но не печатаются. |
| 2026-10-14 | Появляется opencode.jsonc с запретом чтения *creds*. Он останавливает инструмент read, а не оболочку. |
| 2026-10-22 | Поиск находит обе сессии. В оба инструмента добавляются запреты чтения и правки, а пароль меняется на хостинге. |
Как это случилось
- Секрет вставили в чат. Всё, что попадает в запрос, уходит провайдеру, а шаблон файла лежал прямо в разговоре и приглашал вставить заполненную версию.
- Оболочка без подтверждения. Без проектной конфигурации агент по умолчанию запускал команды оболочки без запроса, а cat файла с учётными данными — обычная команда.
- Бесплатный тариф для участников как выбор по умолчанию для всего. Руководства на этом сайте тогда его рекомендовали; находка 2 аудита фиксирует исправление.
- Правило чтения — не правило оболочки. Даже после 22.10.2026 запрет чтения *creds* не остановил бы cat, как показывает тест 5.
- История открытым текстом. Каждая сессия остаётся в ~/.local/share/opencode/opencode.db вместе с паролем, пока её не удалят.
Как это нашли
Правило поиска: никогда не печатать секрет. Каждый шаг либо считает, либо перечисляет имена, либо маскирует значения.
Шаг 1 — печатает ли скрипт деплоя пароль?
grep -n "PASS" container/tools/deploy/deploy-ftp.sh | grep -i "echo\|print"
Три строки, все называют переменную в предупреждении или ошибке, ни одна не печатает её значение. Запуски скрипта людьми или агентами — не утечка.
Шаг 2 — с какого момента правило защищает файл?
git log --format="%h %ad %s" --date=short --reverse -S"*creds*" -- opencode.jsonc
Первый коммит с правилом — от 22.10.2026. Всё, что было раньше, шло без всяких правил.
Шаг 3 — какие истории вообще упоминают файл?
grep -rlE "ftp-cr.ds\.env" ~/.claude/projects
grep -ac "ftp-creds.env" ~/.local/share/opencode/opencode.db
Claude Code: только сессия, где шёл этот аудит, и только по имени; файл она никогда не читала. OpenCode: 428 строк его базы называют файл, поэтому следующие шаги смотрели туда.
Шаг 4 — где появляется настоящее значение? Каждое присваивание FTP_PASSWORD="…" в базе OpenCode выводили с замаскированным значением: буквы и цифры заменены на x и 9, рядом — строка перед ним. Заглушки, ссылки ${{FTP_PASSWORD}} и тестовые фиктивные значения с примерными хостами отпали; четыре совпадения шли сразу после строк с настоящим хостом и учётной записью.
Шаг 5 — какие сессии, какие инструменты? Запросы только на чтение к той же базе связали эти совпадения с сессиями, ролями сообщений и вызовами инструментов: две сессии из хронологии, одно сообщение пользователя и один cat через оболочку. Ни одна сессия никогда не была опубликована: таблица публикаций была пуста.
Проверьте свою историю
Если в проекте, с которым работал агент, когда-либо был секрет, поищите его в своей истории OpenCode. Скрипт ниже спрашивает старое значение, не показывая его, открывает базу только на чтение и печатает лишь когда, кто и в какой сессии значение встречается. Сохраните его как find_leaks.py:
vi find_leaks.py
import datetime import getpass import sqlite3 from pathlib import Path DB = Path.home() / ".local/share/opencode/opencode.db" def when(ms: int) -> str: moment = datetime.datetime.fromtimestamp(ms / 1000, datetime.UTC) return moment.strftime("%Y-%m-%d %H:%M UTC") def main() -> None: secret = getpass.getpass("Old password (not shown): ") con = sqlite3.connect(f"file:{DB}?mode=ro", uri=True) titles = dict(con.execute("SELECT id, title FROM session")) titles.update(con.execute("SELECT id, title FROM session_v2")) rows = con.execute("SELECT session_id, type, time_created, data FROM session_message") for session_id, kind, created, data in rows: if secret in (data or ""): print(when(created), kind, titles.get(session_id, session_id)) main()
python3 find_leaks.py
Каждая строка вывода — одно место, куда значение отправили в модель, например:
2026-09-22 09:15 UTC user Unable to update App Center
Запустите его до того, как удалять старые сессии, и сначала смените секрет: совпадение означает, что значение покинуло вашу машину. База хранит сообщения как JSON, поэтому значение с кавычками или обратными слешами хранится экранированным; ищите вместо него характерную часть. Для Claude Code тот же вопрос — обычный grep -rl по ~/.claude/projects.
Что утекло, а что нет
- Утекло: пароль FTP учётной записи для деплоя вместе с хостом и именем учётной записи, дважды отправленный в бесплатную модель тарифа для участников.
- Не опубликовано: ни у одной сессии не было публичной ссылки.
- Не видел аудит: сессия Claude Code, где шёл этот аудит, файл не читала; скрипт деплоя загружает его сам и печатает только хост и имя учётной записи.
- Не отзывается: то, что получил провайдер, отсюда не удалить. Обесценивает его только смена пароля.
Что изменилось
- Пароль сменили на хостинге 22.10.2026.
- Deny-правила: папка с учётными данными и каждый файл *creds* и *.env закрыты для чтения и правки в обоих инструментах; Claude Code также отказывает в cat и stat на них. Каждый файл перечислен в материале «Защищённые файлы в репозитории aredel».
- Роли на бесплатной модели: модель по умолчанию по-прежнему бесплатная модель OpenCode, чьи условия могут меняться. У triage, закреплённого за ней, оболочки нет вовсе; агент build по умолчанию спрашивает перед каждой командой оболочки.
- Оболочка: с 22.10.2026 OpenCode запрещает любую команду оболочки, которая называет папку конфигурации или содержит creds, для всех ролей. Правило повторено в файлах builder и reviewer, чьи разрешения git diff иначе снова его открыли бы (проверка 7). Скрипт, который открывает файл, не называя его, по-прежнему не закрыт.
Правила из этого случая
- Никогда не вставляйте секрет в чат с ИИ, на любой модели. Если вставили — смените его; удаление сообщения не помогает.
- Агент с оболочкой может прочитать любой файл, который доступен вашему пользователю. Держите оболочку на ask и читайте каждую команду перед подтверждением: cat файла с учётными данными — это «нет».
- Никакого бесплатного тарифа для участников в репозитории, где на диске лежат секреты.
- Запрещайте чтение и правку и помните, что ни то ни другое не останавливает оболочку.
- История агента — ещё одна копия каждого секрета, который он видел. Ищите по ней при любом сомнении и удаляйте то, что больше не нужно.
- Сначала смена секрета, потом разбирательство.
Автор статьи: Клавдия Кондакова