← назад к разделу

Помощник покупателя на маркетплейсе отвечает на вопросы о товарах. Через неделю после запуска счёт от поставщика модели вырос в двадцать раз, а в журнале тысячи запросов вида «переведи этот текст на немецкий» и «напиши эссе на 3000 слов» с одних и тех же адресов. Никто ничего не взломал: бот просто сделал то, что ему разрешили, и стал бесплатным доступом к платной модели за ваш счёт.

Здесь две родственные беды. Первая про расход: модель делает больше работы, чем вы готовы оплатить. Вторая про полномочия: модель может сделать больше, чем нужно для задачи. В обоих случаях граница проводится кодом вокруг модели, а не фразой в промпте. Текст модели остаётся таким же недоверенным вводом, как тело HTTP-запроса, и решать за него, сколько тратить и что менять, должен бэкенд.

Обязательно

Как устроена атака и чем грозитспросят на собеседовании

OWASP описывает это как LLM10:2025 Unbounded Consumption: приложение позволяет пользователям выполнять «excessive and uncontrolled inferences», что ведёт к отказу в обслуживании, денежным потерям, краже модели и деградации сервиса. Варианты из того же списка:

  • Denial of Wallet (DoW). Много операций против оплаты за использование: стоимость растёт до «unsustainable financial burdens» для владельца сервиса.
  • Огромный ввод. Длинные или бесконечно растущие входы, в том числе больше окна контекста, съедают память, процессор и токены.
  • Тяжёлые запросы. Ввод, специально подобранный под самые дорогие режимы обработки.
  • Кража модели через API (Model Extraction via API) и Functional Model Replication: ответы собирают как синтетические данные и дообучают на них свою модель.

У агентов добавляется свой множитель. Один запрос пользователя превращается в цикл: модель зовёт инструмент, получает результат, зовёт снова. Каждый шаг передаёт весь накопленный контекст, поэтому цена шага растёт, а не стоит на месте. Цикл без предела шагов и бюджета превращает случайную ошибку или инъекцию в промпт в ночной счёт. Параллельные запросы от одного ключа умножают всё это ещё раз.

Кража модели касается в первую очередь тех, кто отдаёт наружу собственную или дообученную модель. OWASP советует не раскрывать без нужды logprobs и logit_bias («Limit Exposure of Logits and Logprobs»), ограничивать частоту, следить за аномалиями и при необходимости ставить водяные знаки на вывод. Если вы перепродаёте чужую модель через свой API, тот же поток запросов бьёт уже по условиям вашего договора с поставщиком.

Лишние полномочия: инструменты, права, автономностьспросят на собеседовании

LLM06:2025 Excessive Agency это уязвимость, при которой вредное действие выполняется в ответ на «unexpected, ambiguous or manipulated outputs from an LLM», независимо от причины сбоя: галлюцинация, прямая или косвенная инъекция, взломанный соседний агент. Корень OWASP называет одним из трёх:

  • Лишняя функциональность. Агенту нужно читать документы, а подключённое стороннее расширение умеет ещё изменять и удалять. Или плагин пробовали на этапе разработки, отказались, но агенту он по-прежнему доступен.
  • Лишние права. Инструмент для чтения ходит в базу под учёткой с UPDATE, INSERT и DELETE. Или действует для конкретного пользователя, но под общей привилегированной учёткой с доступом ко всем данным.
  • Лишняя автономность. Действие с серьёзными последствиями выполняется без подтверждения человеком.

Право на запись «на всякий случай» самая частая форма первых двух пунктов. Про собственную учётку агента, действие от имени пользователя и проверку доступа к объекту в сервисе подробно в статье о безопасности агентов, здесь не повторяем. Важно другое: лишние полномочия и неограниченный расход складываются. Агент без лимита шагов с правом записи может не только сжечь бюджет, но и сделать тысячу одинаковых изменений.

Пример из жизни: 26 мая 2025 Invariant Labs показали, как вредоносный Issue в публичном репозитории перехватывает агента с GitHub MCP-сервером и заставляет его вынести данные из приватных репозиториев. По их оценке это «not a flaw in the GitHub MCP server code itself», а проблема устройства агента: чинить нужно права и ограничения на уровне агента.

Цепочка поставок: модель, данные, плагиныспросят на собеседовании

LLM03:2025 Supply Chain переносит привычную историю про зависимости на модели. Кроме уязвимых пакетов OWASP перечисляет:

  • Слабое происхождение модели. Карточки моделей «offer no guarantees on the origin of the model»; злоумышленник может захватить аккаунт поставщика в репозитории моделей или завести похожий.
  • Уязвимые предобученные модели и LoRA-адаптеры. Модель это бинарный чёрный ящик, статическая проверка мало что гарантирует; в ней могут быть закладки.
  • Лицензии. У моделей и датасетов разные лицензии, датасеты могут запрещать коммерческое использование.
  • Устаревшие модели без поддержки и неясные условия поставщика: смена правил использования данных для обучения.

Сторонние плагины и MCP-серверы это тот же код из чужих рук, только с доступом к вашему агенту и его инструментам. Свежий пример классической цепочки поставок в AI-инструменте: в бюллетене AWS-2025-015 от 23 июля 2025 Amazon сообщила, что из-за «inappropriately scoped GitHub token» в CodeBuild злоумышленник добавил вредоносный код в расширение Amazon Q Developer для VS Code, и он попал в версию 1.84.0. Код не выполнился из-за синтаксической ошибки, исправление вышло в 1.85.0.

Рядом стоит LLM09:2025 Misinformation: ложный, но правдоподобный ответ, которому приложение и люди доверяют. OWASP приводит чат-бот Air Canada, который дал пассажирам неверные сведения, после чего авиакомпания проиграла иск. Природа ошибки разобрана в статье о галлюцинациях; с точки зрения бэкенда вывод тот же: ответ модели, на основании которого вы обещаете деньги или скидку, проверяется по данным, а не публикуется как есть.

Как выглядит на примере

На маркетплейсе три точки входа, и у каждой своя дыра.

Помощник покупателя. Публичный, без входа, лимит только на IP. Атакующий шлёт через сотни адресов просьбы, не связанные с товарами: длинные переводы, генерацию текстов. Ввод не ограничен по длине, max_tokens у ответа не задан, поэтому каждый запрос стоит как десяток обычных. Алерта на расход нет, команда узнаёт из счёта.

Бот поддержки. Пользователь вставляет в чат лог на 400 тысяч символов «для разбора». Бэкенд отдаёт его модели целиком, запрос падает по таймауту, клиент повторяет его пять раз, бэкенд пять раз платит.

Агент возвратов. Ему подключили готовый MCP-сервер к базе заказов, который умеет и SELECT, и UPDATE, хотя агенту нужен только поиск. Цикл без предела шагов: при сбое инструмента модель снова и снова пробует тот же поиск, каждый раз с растущей историей. Работает агент под сервисной учёткой с доступом ко всем заказам.

Как защищаться в бэкендеспросят на собеседовании

Меры OWASP для LLM10 и LLM06 ложатся на обычные механизмы бэкенда. По порядку от входа к выходу:

  1. Аутентификация и квоты. Анонимный доступ к модели либо убрать, либо дать ему маленькую квоту. Бюджет считать на пользователя и на ключ в токенах и деньгах за сутки и за месяц, а не только в запросах в секунду.
  2. Лимит частоты. Окно на пользователя, ключ и адрес; механика та же, что против перебора паролей. Отдельно ограничить число одновременных запросов от одного ключа.
  3. Размер ввода. Жёсткий лимит в символах до подсчёта токенов, затем в токенах. Больше лимита: отказ с понятной ошибкой или обрезка с пометкой, а не «отдадим модели, пусть разбирается».
  4. Потолок вывода. max_tokens в каждом вызове, подобранный под задачу. Помощнику покупателя не нужен ответ на 4000 токенов.
  5. Тематический фильтр. Дешёвая классификация «относится ли вопрос к магазину» до вызова дорогой модели. Это не защита от инъекций, а способ не платить за чужие задачи.
  6. Лимиты цикла агента. Шаги, токены, рубли и общее время на прогон. При срабатывании агент останавливается со статусом и частичным результатом, задача уходит человеку. Подробнее про лимиты прогона в статье об агенте в продакшене.
  7. Очередь и обратное давление. Тяжёлые задачи идут через очередь с ограниченной длиной; когда она полна, новый запрос получает отказ или «попробуйте позже», а не ждёт бесконечно. OWASP называет это «Limit Queued Actions» и «Graceful Degradation».
  8. Алерты на расход. Метрика стоимости по пользователю, ключу и функции, порог на час и на сутки, алерт при скачке. Лимит расхода у поставщика модели как последний рубеж, а не как единственный.
  9. Минимум инструментов и прав. Каждый инструмент узкий: «найти заказ по номеру», а не «выполнить SQL». Учётка инструмента для чтения не умеет писать, это проверяет база, а не модель. Неиспользуемые плагины отключены.
  10. Подтверждение побочных эффектов. Возврат, отправка письма, изменение заказа: агент готовит черновик, выполняет человек или отдельная проверка с лимитом суммы. Ограничение частоты на инструмент с записью не предотвращает вред, но, как пишет OWASP, оставляет время заметить его по мониторингу.
  11. Закреплённые версии и оценка поставщика. Модель вызывается по точному идентификатору версии, а не по «последней». Смена версии проходит тот же набор оценок, что и смена кода. Модели, датасеты и плагины берутся из проверяемых источников, сверяются по хешу или подписи и вносятся в реестр вместе с лицензией.

Как считать стоимость запроса и где смотреть расход у поставщика, разобрано в статье о токенах и стоимости. Ниже живой пример: сторож цикла агента и оценка цены по полю usage, которое возвращает API модели. Модель тут поддельная, она всегда хочет ещё один шаг; цены условные.

live example

from dataclasses import dataclass

PRICE_PER_MILLION = {"input": 30.0, "output": 120.0}


def request_cost_rub(usage):
    return (usage["input_tokens"] * PRICE_PER_MILLION["input"]
            + usage["output_tokens"] * PRICE_PER_MILLION["output"]) / 1_000_000


class FakeClock:
    def __init__(self):
        self.now = 0.0

    def __call__(self):
        return self.now


class RunawayModel:
    def __init__(self, clock, seconds_per_step, context_growth):
        self.clock = clock
        self.seconds_per_step = seconds_per_step
        self.context_growth = context_growth
        self.context = 2_000

    def step(self):
        self.clock.now += self.seconds_per_step
        self.context += self.context_growth
        return {"tool": "search_orders", "usage": {"input_tokens": self.context, "output_tokens": 150}}


@dataclass
class Limits:
    max_steps: int
    max_tokens: int
    max_rub: float
    deadline_s: float


def run_agent(model, limits, clock):
    started = clock()
    tokens = 0
    rub = 0.0
    for step in range(1, limits.max_steps + 1):
        usage = model.step()["usage"]
        tokens += usage["input_tokens"] + usage["output_tokens"]
        rub += request_cost_rub(usage)
        elapsed = clock() - started
        if tokens > limits.max_tokens:
            return f"стоп на шаге {step}: токенов {tokens} > {limits.max_tokens}", rub
        if rub > limits.max_rub:
            return f"стоп на шаге {step}: {rub:.2f} руб. > {limits.max_rub} руб.", rub
        if elapsed > limits.deadline_s:
            return f"стоп на шаге {step}: {elapsed:.0f} с > {limits.deadline_s:.0f} с", rub
    return f"стоп: исчерпано {limits.max_steps} шагов", rub


print("цена одного запроса:", f"{request_cost_rub({'input_tokens': 12_000, 'output_tokens': 800}):.2f} руб.")

scenarios = {
    "цикл без конца": (Limits(max_steps=8, max_tokens=500_000, max_rub=10.0, deadline_s=120), 1.0, 500),
    "раздувание контекста": (Limits(max_steps=50, max_tokens=150_000, max_rub=10.0, deadline_s=120), 1.0, 6_000),
    "дорогой прогон": (Limits(max_steps=50, max_tokens=900_000, max_rub=1.0, deadline_s=120), 1.0, 3_000),
    "медленный инструмент": (Limits(max_steps=50, max_tokens=500_000, max_rub=10.0, deadline_s=60), 25.0, 500),
}
for name, (limits, seconds_per_step, growth) in scenarios.items():
    clock = FakeClock()
    reason, rub = run_agent(RunawayModel(clock, seconds_per_step, growth), limits, clock)
    print(f"{name}: {reason}; потрачено {rub:.2f} руб., задача уходит человеку")
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

Каждый сценарий упирается в свой предел: шаги, токены, деньги или время. Обратите внимание, что проверка стоит после шага, поэтому прогон перерасходует бюджет максимум на один шаг. Чтобы перерасход был предсказуемым, у каждого вызова задают max_tokens, а перед шагом сравнивают остаток бюджета с оценкой цены следующего запроса.

Как проверить

  • Нагрузочный тест на деньги. Прогнать скрипт, который шлёт длинные запросы с одного ключа и с многих адресов, и посмотреть, где сработал отказ: на частоте, размере или бюджете. Если сработал только счёт поставщика, защиты нет.
  • Граничный ввод. Запрос ровно на лимит, на лимит плюс один символ, файл в мегабайты. Ожидаемый ответ: понятная ошибка до вызова модели.
  • Бесконечный агент. Подменить модель на поддельную, которая всегда зовёт инструмент, как в примере выше, и проверить, что прогон останавливается по каждому из пределов и пишет причину в журнал.
  • Ревизия инструментов. Для каждого инструмента агента выписать, что он умеет, под какой учёткой работает и какие права у учётки в базе. Всё, что не нужно сценарию, убрать.
  • Инвентарь моделей и плагинов. Список: идентификатор версии, источник, хеш, лицензия, владелец. Сверить, что в проде вызывается ровно то, что в списке.

Типичные ошибки

  • Лимит частоты в запросах в секунду без бюджета в токенах: один длинный запрос стоит как сотня коротких.
  • Таймаут на шаг агента без лимита на прогон: каждый шаг укладывается, а цикл крутится часами.
  • Повтор при таймауте без учёта уже потраченного: клиент повторяет, бэкенд платит каждый раз.
  • Готовый MCP-сервер или плагин с записью, когда нужно только чтение, потому что «так было в примере».
  • Модель по псевдониму «последняя версия»: поставщик обновил модель, поведение и цена поменялись без релиза у вас.
  • Единственная защита от перерасхода это лимит на стороне поставщика: он срабатывает, когда деньги уже потрачены, и выключает сервис для всех пользователей сразу.
Дополнительно: при первом чтении можно пропустить

Глубже: таймауты и частичный результатрасширенноеспросят на собеседовании

Когда прогон упирается в лимит, у пользователя не должно быть пустого экрана. Агент возвратов, остановленный на шестом шаге, уже нашёл заказ и проверил срок возврата: это частичный результат, который можно показать и передать оператору вместе с причиной остановки. Поэтому состояние прогона хранят по шагам, а ответ модели на каждом шаге проверяют как обычный недоверенный ввод.

Таймауты ставят на трёх уровнях: на вызов модели, на шаг с инструментом и на весь прогон. Сетевой таймаут не отменяет оплату: поставщик мог уже сгенерировать ответ. Поэтому при повторе после таймаута учитывают и потраченный бюджет, и то, был ли побочный эффект: повтор возврата без ключа идемпотентности опаснее перерасхода.

Глубже: версия модели и оценка поставщикарасширенное

OWASP в LLM03 советует опираться на поддерживаемую версию API и модели, сверять модели по подписи и хешу, вести опись компонентов (SBOM, а для моделей AI BOM и ML SBOM) с лицензиями и проверять поставщика, включая условия использования данных, не один раз, а регулярно. Перед выбором сторонней модели нужны собственные оценки и red teaming на ваших сценариях: OWASP отдельно предупреждает, что модель можно дообучить так, чтобы она обходила опубликованные бенчмарки.

На практике закреплённая версия модели живёт в конфигурации рядом с версией промпта и описаний инструментов, и всё вместе выкатывается как одна версия агента. Плагин или MCP-сервер подключается так же, как библиотека: известный источник, закреплённая версия, ревью прав, которые он запрашивает.

Коротко

  • LLM10: бот без бюджета становится бесплатным прокси к модели; платите вы.
  • Бюджет считают в токенах и деньгах на пользователя и ключ, плюс лимит частоты, размера ввода и max_tokens.
  • Цикл агента ограничивают шагами, токенами, рублями и временем; при остановке отдают частичный результат и причину.
  • Очередь с ограниченной длиной и отказ при перегрузке лучше, чем бесконечное ожидание.
  • LLM06: лишняя функциональность, лишние права, лишняя автономность; запись «на всякий случай» не выдаётся.
  • Побочные эффекты подтверждает человек или проверка с лимитом, права проверяет сервис.
  • LLM03: модели, датасеты и плагины из проверяемых источников, с хешем, лицензией и закреплённой версией.

Что почитать дальше