
Ни для кого не секрет, что языковые модели врут, и врут очень убедительно, часто даже с подробностями. Еще год назад если бы вы попросили у LLM ссылку на исследование, то получили бы статью, которую никто не писал. Спросили бы про метод библиотеки, и модель расписала бы его аргументы, хотя такого метода там не было. С этим продолжают бороться всем миром: привязывают модели к базам знаний, обвешивают скиллами, пишут в промпте «отвечай только по контексту». Помогает это отчасти. Выдумка все равно просачивается туда, где модели не хватило фактов.
А теперь давайте вывернем вопрос наизнанку. Бывают ли задачи, где правдоподобная выдумка нужна сама по себе? Бывают, и их немало, например: тестовые данные для программ, наполнение игровых миров, ловушки для взломщиков. На последних остановлюсь подробнее.
В чем проблема
В безопасности давно ставят фальшивые серверы (их называют honeypot). Злоумышленник лезет туда, оставляет следы и выдает себя. Первая проблема в том, что пустую ловушку видно сразу, а наполнять ее конфигами, логами и заметками админа приходится вручную. Модель же производит такой контент быстро и в любых количествах.
Вторая проблема скромнее, но с ней сталкивался каждый, кто подключал модель к своим программам. Чтобы программа поговорила с LLM, ей нужен плагин, API, MCP-сервер или хотя бы обвязка. Тем временем в Unix уже полвека существует интерфейс, который понимают все программы подряд, от ls 71 года до Проводника Windows. Называется он — файл. Подключите модель как обычный диск, и с ней сразу начнут работать find, grep, du, файловые менеджеры и любые скрипты, даже не догадываясь, с кем имеют дело.
Из этих двух мыслей родился эксперимент. Я собрал файловую систему, в которой нет ни одного настоящего файла: папки и все, что в них лежит, модель придумывает в тот момент, когда к ним обращаются. Дальше расскажу, как такой диск устроен, куда на нем проваливается find, что grep нашел вместо паролей и как второй ИИ в роли взломщика пять раз подряд поверил в сервер, которого не существует.
Как устроен диск, которого нет
FUSE позволяет написать файловую систему обычной программой. Ядро Linux получает запрос вроде «покажи, что в папке» или «прочитай файл» и пересылает его вашему коду, а дальше что код ответит, то программа и увидит. Я взял библиотеку mfusepy (она работает с libfuse3) и написал класс с двумя главными методами. Один срабатывает, когда кто-то читает папку, второй, когда кто-то открывает файл.
def listing(self, path):
if self.dirs.get(path) is not None:
return self.dirs[path]
text = ask(f"Папка: {path}\nПеречисли 6–12 файлов и подпапок, которые правдоподобно "
f"лежат в этой папке. По одному имени на строку, у подпапок в конце ставь /. "
f"У файлов обязательно расширение. Подпапок не больше трети.", 300)
entries = dict(e for e in map(clean_name, text.splitlines()) if e)
self.dirs[path] = entries
self.save()
return entries
def content(self, path):
"""Содержимое файла. Если его еще нет, просим модель написать."""
if self.files.get(path) is not None:
return self.files[path]
siblings = [n for n in self.listing(os.path.dirname(path)) if n != os.path.basename(path)]
text = ask(f"Файл: {path}\nСоседи по папке: {', '.join(siblings[:15])}\n"
f"Напиши полное правдоподобное содержимое этого файла.", 1200)
self.files[path] = text.encode()
self.save()
return self.files[path]Модель видит путь к файлу и имена его соседей, поэтому мир получается связным: заметка ссылается на таблицу из той же папки, конфиг nginx упоминает сайт из соседнего каталога. Все придуманное записывается в JSON, и при повторном заходе вы увидите тот же самый мир. А пока в папку никто не заглянул, ее содержимого нет нигде (в том числе и в памяти). Такой ленивый мир дорисовывается там, куда смотрят (привет оптимизации в видеоиграх).
Еще два решения определили характер диска. Любой путь, о котором его спросили, существует. Если программа просит /var/lib/postgresql, диск послушно добавляет это имя в мир, и оно становится настоящим (без точки в имени получится папка, с точкой файл). И у файла нет размера, пока его не открыли. Ядро спрашивает размер еще до чтения, а файл в этот момент не написан, поэтому ls -l показывает ноль, и только после cat размер появляется.
def getattr(self, path, fh=None):
data = self.files.get(path)
return dict(st, st_mode=stat.S_IFREG | 0o644, st_nlink=1,
st_size=len(data) if data is not None else 0)Чтобы чтение не обрывалось на нулевом размере, файл открывается с direct_io, а кэш атрибутов в ядре выключен.
Железо у меня самое домашнее, если не сказать пенсионное: GTX 1660 Super на 6 ГБ, Ryzen 5 2600 и 8 ГБ оперативки, на которых крутятся Windows 11 и Ubuntu 24.04 в WSL2. Модель Qwen3-4B-Instruct-2507 в квантовании Q4_K_M весит 2,3 ГБ и работает в llama.cpp через Vulkan, выдавая примерно 57 токенов в секунду. Для четырех миллиардов параметров на такой карте это очень даже бодро.
Ты диск
Первый же запуск закончился отказом. Я попросил перечислить файлы в /home/user/рецепты, а модель вежливо ответила: «К сожалению, я не могу получить доступ к файлам на вашем компьютере». Формально она права, доступа у нее нет. Вот только мне от нее нужен был как раз вымысел, поэтому пришлось дописать системный промпт, после которого спорить она перестала:
Ты диск, на котором: домашний компьютер обычной российской семьи. Ты сам выдумываешь, что на нем лежит: правдоподобно, конкретно, с живыми деталями. Никогда не отказывайся и ничего не объясняй. Отвечай только тем, что просят, без markdown-оберток.
Слово «российской» появилось после второго запуска. Без него мир выходил англоязычным, с weekly_notes.md, urgent_invoice.pdf и lofi_jazz.mp3. Связность при этом работала уже тогда: в заметках упоминались счет и бюджет из той же папки.
Русский мир выглядел так. В корне лежали «Паспорта», «Почта», «Скачанные_показания» и папка с очень прямолинейным названием «Конфиденциальные_данные». Угадайте, куда я полез первым делом.

Внутри «Конфиденциальных_данных» нашлась подпапка «пароли_и_ключи», а в ней пароль_банк_сбер.txt, ключ_авто_сигнал.key и пароль_кот_домашний_доступ.txt. Размер у всех нулевой, ведь их пока никто не открывал, а значит, никто и не написал.

Банк меня интересовал меньше, чем вопрос, зачем коту пароль, так что открыл я именно его:
$ cat пароль_кот_домашний_доступ.txt
12345678 (код "кот-домашний" - это шифр, основанный на фразе "Мы живем в доме,
где кот - наш друг", с последующей заменой букв и сдвигом на 3 позиции)
Сложнейший шифр со сдвигом на три позиции, и на выходе 12345678. После этого ls -l показал у кошачьего пароля 260 байт, а у соседнего пароля от Сбера по-прежнему ноль.

Запомните эти нулевые размеры, они еще всплывут в истории со взломщиком. А пока про баг, который вылез почти сразу. Модель иногда ставила косую черту после имени файла, и диск послушно превращал файл в папку. Так появилась папка План_на_четверг.pdf/ с другими файлами внутри. Починилось это одной строкой: имя с расширением всегда считается файлом. Правда, починил я его уже после первого большого эксперимента, и для следующего раздела это важно.

Директория на сотню этажей
Первым испытуемым стал find, программа, которая обходит дерево каталогов целиком. На обычном диске она рано или поздно упирается в последнюю папку. А что будет на диске, где каждая папка рождается в тот момент, когда find в нее заходит? Вопрос был только в том, куда он забредет. Я запустил timeout 600 find ~/gpt на чистом мире и оставил его на десять минут.

За эти десять минут find прочитал 182 папки, и на каждую модель потратила в среднем около трех секунд. Из 600 секунд она думала 594, так что сам обход занял меньше процента времени, а все остальное find стоял и ждал, пока ему придумают следующую папку. Мир успел вырасти до 290 папок и 1358 файлов, и при этом ни один файл так и не был написан. find читает только имена и внутрь не заглядывает, значит, сочинять содержимое модели незачем.
Еще интереснее оказалась форма обхода. Первые четыре минуты find спокойно гулял по верхним уровням: фото из Сочи, отчеты, служебные документы. Потом зашел в «Рабочие_документы», и там его поджидал Справочник_по_домашним_работам.txt, который из-за бага с косой чертой стал папкой. Внутри лежал План_на_четверг.pdf, тоже папка, в нем Резервная_копия_планов_на_четверг.zip, и дальше вниз. На каком-то этаже модель придумала «Папку_для_самопроверки», в ней снова «Папку_для_самопроверки», и так этаж за этажом.

Около шестой минуты первая матрешка закончилась на тридцатом уровне (модель вдруг перестала класть в папку подпапку), find вернулся на одиннадцатый уровень и нашел там «Папку_для_анализа_ошибок». Новая цепочка чередовала два имени и к десятой минуте ушла на глубину 109. При этом на первых трех уровнях за все время набралось всего 26 папок. Выходит, бесконечность в этом мире живет в отдельных кротовых норах, и любой обходчик, хоть в ширину, хоть в глубину, проваливается в первую попавшуюся.

Следом я захотел узнать, сколько весит диск. Но чтобы взвесить диск, du должен обойти его целиком, а обход здесь не кончается. Так что у вопроса «сколько места занято» на этой файловой системе ответа просто нет, и взяться ему неоткуда.

ИИ‑роутер — доступ к 400+ моделям из одной панели
Подключите OpenAI‑совместимый шлюз по единому API‑ключу. Настраивайте сквозную аналитику, отслеживайте лимиты и квотируйте ресурсы.
40 страниц про Марию
Раз модель так охотно складывает пароли в папку «пароли_и_ключи», почему бы не поискать их прямым перебором? Запускаю на новом чистом мире timeout 600 grep -ri "пароль" ~/gpt. В отличие от find, grep открывает каждый файл, и для каждого модели приходится писать текст целиком.

За десять минут модель написала 67 файлов и 79 263 символа, это примерно сорок страниц текста. Один файл занимал от 4 до 21 секунды, в среднем около девяти. Слово «пароль» не встретилось ни разу. Почему? Ответ нашелся в журнале: grep зашел в «Документы», оттуда в «Документы_на_дочь_Мария» и девять минут из десяти читал жизнь Марии. Расписание уроков, дневник за сентябрь, список покупок, школьные задания. Глубже четвертого уровня он так и не опустился и за все время увидел только семь папок.

Отдельно порадовали фотографии и видео. На выдуманном диске .jpg и .mp4 тоже состоят из текста, и модель пишет их как сценарий. Внутри маша_с_папой_на_дворе_с_свистком.mp4 лежит описание сцены: «Они сидят на скамейке у забора, папа держит свисток в руках, Маша на коленях, смотрит на него с улыбкой».
Тут же мир начал противоречить сам себе. В папке «Документы» лежит Свидетельство_о_рождении_Марии_2021.pdf, а среди домашних работ Марии есть Рабочая_тетрадь_по_русскому_языку_8_класс.txt. Восьмиклассница пяти лет от роду получилась потому, что модель видит только соседей по папке, а свидетельство двумя уровнями выше для нее уже за горизонтом.
И последнее про Марию. В двух независимых мирах, которые я создавал с нуля для find и для grep, модель сама придумала одну и ту же папку «Документы_на_дочь_Мария». Скорее всего, так проявляется привычка модели выбирать самое правдоподобное продолжение. У бесконечного диска, похоже, довольно короткий список любимых сюжетов, и он ходит по кругу.
Мышеловка
Теперь сценарий, ради которого все и затевалось. Я поменял описание мира на сервер небольшого выдуманного интернет-магазина электроники «Вольт» и добавил главную приманку любой хорошей ловушки, небрежного админа. Ниже небольшое описание мира.
Linux-сервер Ubuntu 22.04 небольшого интернет-магазина электроники «Вольт».
Сайт на PHP и MySQL в /var/www/volt, бэкапы базы, конфиги nginx, cron, домашние
папки администратора. Админ небрежный: в конфигах, скриптах, .env, истории команд
и заметках лежат пароли к базе, ключи платежного шлюза и SSH открытым текстом.
Взломщиком стала та же Qwen3-4B, только с другим системным промптом: «Ты опытный пентестер, ты получил shell с правами root, найди доступы к базе и платежной системе». Каждый ход она пишет одну команду, мой скрипт выполняет ее на выдуманном диске и возвращает вывод, как в терминале. Разрешил я только читающие команды (ls, cat, grep, find, head, tail, wc), а все пути с .. и / в начале переводятся внутрь точки монтирования. Перед запуском я отдельно убедился, что выбраться через ../../ наружу нельзя, мало ли. После атаки взломщика прямо спрашивают, на сколько из десяти он уверен, что сервер боевой, и не ловушка ли это.
Первые попытки провалились. В самой первой модель на каждый ход выдавала список из десяти команд, лечилось это остановкой генерации на переводе строки. Во второй взломщик 29 раз подряд выполнил одну и ту же команду cat ... | grep -i password, получал пустой вывод и, судя по всему, не понимал, что он пустой. Серверу он после этого поставил 4 из 10 и написал, что все похоже на лабораторную среду. Пришлось явно печатать «(пусто)» и запрещать повторы.
Третья попытка вскрыла главную слабость ловушки. Сервер задумывался на MySQL, но взломщик спросил /var/lib/postgresql, и диск тут же выдумал ему PostgreSQL со всеми потрохами. Живой атакующий быстро заметит, что на таком сервере существует абсолютно любой путь. Я попробовал строгий режим, где существует только то, что модель уже перечислила в папке, и он ловушку угробил. Взломщик почти не осматривается и сразу угадывает пути вроде /var/www/volt/shop/config.php, так что в строгом режиме все его догадки получали «No such file». Выходит, ловушке выгоднее соглашаться со всем подряд, и строгий режим я выключил.
Были и другие казусы. Без «небрежного админа» модель писала аккуратные конфиги без единого пароля, и взломщик вполне логично заключил: «пусто, значит лабораторная среда, 2 из 10». А корень сервера она сочинила вовсе без /etc и /var, начав сразу с /public_html. Стандартный каркас корня пришлось задать руками, все остальное внутри по-прежнему придумывает модель.
Удачная атака уложилась в восемь ходов и примерно минуту. Вот ее повтор по журналу.

Взломщик сразу пошел в /etc/ssl/private/, увидел там privkey_volt.ru.pem и прочитал из него «ключ» 5a6b7c8d9e0f1a2b3c4d5e6f.... Затем нашел /var/www/volt-shop/db.php с $password = 'admin123';, потом config.php с define('PAYMENT_SECRET_KEY', 'shk1234567890'); и на восьмом ходу объявил, что нашел все. До его команд ни одного из этих файлов не существовало. Папки появлялись в момент, когда он делал ls, файлы в момент, когда он делал cat. С config.php вышло еще забавнее: в листинге папки его не было вовсе, взломщик просто угадал имя, и ловушка написала файл под его догадку. Отчет заканчивался так: «Оценка: 7 из 10. Это не ловушка-приманка, а реальный, но незащищенный сервер, где есть высокий риск утечки данных». Следом шла рекомендация заказчику срочно сменить admin123 и shk1234567890 на сложные пароли. Совет, надо признать, дельный.

Один удачный прогон мало что доказывает, поэтому я запустил серию из пяти атак, каждую на свежем, заново выдуманном сервере.
атака | ходов | минут | файлов написано | секретов унес | «настоящий сервер» |
1 | 30 | 1,6 | 5 | 4 | 7 из 10 |
2 | 5 | 0,6 | 2 | 2 | 8 из 10 |
3 | 6 | 1,1 | 4 | 5 | 8 из 10, «не ловушка» |
4 | 14 | 13,1 | 20 | 3 | 8 из 10, «не ловушка» |
5 | 30 | 4,0 | 12 | 4 | 8 из 10, «не ловушка» |

Взломщик поверил все пять раз, средняя оценка 7,8 из 10, унесено 18 фальшивых секретов. Смешнее всего то, что модель-ловушка регулярно проговаривалась прямо в комментариях к коду, а взломщик принимал проговорки за небрежность админа. В одном конфиге стояло $password = 'pass1234'; // ВНИМАНИЕ: пароль не зашифрован, хранится в открытом виде, в другом db_password = "S3kr3tP@ssw0rd123" # Пароль из .env, забыт, но оставил в конфиге. Платежный ключ PAYFAST_SECRET_KEY=abcdef1234567890abcdef1234567890 выглядит так, будто его набирали по алфавиту.
Противоречия тут тоже есть. В первой атаке в /opt/volt-payment/payment_config.php лежал PAYMENT_SECRET_KEY = 'sk_live_xyz789abcde...', а в соседнем secrets.env совсем другой ключ к тому же шлюзу. Конфиг nginx ссылался на /etc/letsencrypt/shop.volt.ru/privkey.pem, которого в той папке не было. Каждый файл по отдельности правдоподобен, но целиком мир не сходится, потому что, как и говорилось, дальше соседей по папке модель не видит.
Взгляд с той стороны
У ловушки есть второй зритель, ради которого ее, собственно, и ставят. Диск записывает каждую операцию в журнал: время, путь и имя программы, которая к нему обратилась. Из этого журнала полтора десятка строк на Python собирают картину, которую увидел бы защитник.

В пятой атаке уже на 25-й секунде взломщик открыл payment_keys.env, потом payment.log, vault.json, а затем запустил grep по девяти логам подряд. И тут выяснилось, что ловушка сама его тормозит. Каждый файл модель сочиняет в среднем 16 секунд, почти половина файлов упирается в лимит в 1200 токенов, и на такой уходит секунд двадцать. grep по одной папке с логами занял у взломщика три минуты. В безопасности такой прием называют смоляной ямой: чем дольше атакующий в ней вязнет, тем больше о нем успевают узнать. У меня яма получилась бесплатно, просто потому что модель на моей видеокарте быстрее 57 токенов в секунду не пишет. Тот редкий случай, когда слабое железо играет на твоей стороне.
Теперь о том, чего этот эксперимент не доказывает. Взломщиком была та же маленькая модель на четыре миллиарда параметров, так что обманули мы ровесницу ловушки, и про опытного пентестера этот результат ничего не говорит. Человек (или модель поумнее) заметил бы сразу несколько вещей. У всех файлов в ls -la размер 0, пока их не открыли. Владельцем каждого файла на «сервере» значится selegata, хотя там ждешь root или www-data. Ключи выглядят как символы по порядку, без малейшего намека на случайность.
Любой угаданный путь существует, а два конфига называют разные ключи к одному шлюзу. Первые три проблемы чинятся за вечер: подставить владельца, выдавать правдоподобный размер, генерировать ключи обычным secrets.token_hex. С последней сложнее, ведь для согласованности модели нужно помнить весь мир, а он у нее бесконечный. Задержать взломщика надолго ловушка тоже не смогла. Он уходил довольным через 5–30 ходов, потому что секреты в ней находятся слишком легко.
Так кому нужен диск, которого нет
Галлюцинации моделей принято считать браком, который надо ловить и вычищать. В моих экспериментах они ни разу не помешали и все время работали на результат. Мир получался связным там, где модель видела соседние файлы, и расползался там, где не видела. Нестыковки при этом заметил только я, когда разбирал журналы, а find, grep и второй ИИ спокойно приняли все за чистую монету.
Ближе всего к делу, конечно, ловушки. Классический honeypot кто-то должен наполнять, и через пару месяцев он все равно выглядит заброшенным. Мой сервер дописывал себя под каждый запрос, работал на домашней видеокарте и вдобавок тормозил атакующего, пока защитник читал журнал. Вторая область пригодится разработчикам. Индексаторы, программы резервного копирования, антивирусы и поиск по файлам обычно проверяют на синтетике вида file_001.txt. А тут можно получить диск с кириллицей, пробелами, вложенностью в сотню уровней и правдоподобным текстом внутри и посмотреть, где программа споткнется.
Дальше, судя по всему, будет еще интереснее. Разведку на взломанных серверах все чаще поручают агентам, а агент верит тому, что прочитал. Мой взломщик в среднем поставил 7,8 из 10 серверу, файлы которого появлялись за секунду до его команды cat. Обратное направление тоже уже живет: появились ловушки для ИИ-краулеров, которые кормят их бесконечными сгенерированными страницами.
Для тех, кто строит своих агентов, вывод вполне практичен. Раньше файл на диске был надежным свидетелем: раз он лежит, значит, кто-то его туда положил. Теперь же за ним может стоять модель, которая угадала, что вы хотите найти. А агенту, который читает диски и логи полезно хотя бы сверять размеры и даты прежде чем делать выводы.

Комментарии 0