Глава 8

Identity приходит из сессии, не из аргументов модели

Модель может выбрать order_id - это её работа. Но она не должна сообщать actor_id, tenant или уровень доступа: эти значения формируются сервером из проверенной сессии и замыкаются внутри handler, куда модель дотянуться не может. Разница принципиальная: order_id - это намерение, а identity - это право, и право не приходит из вероятностного текста.

В коде это значит, что handler получает доверенный контекст отдельным аргументом, а запрос к базе всегда сужен по actor и tenant.

TypeScript
type TrustedToolContext = {
  actorId: string;
  tenantId: string;
  locale: "ru" | "en" | "uz";
  traceId: string;
};

const orderReadInput = z.object({
  order_id: z.string().min(6).max(40)
}).strict();

async function orderRead(
  raw: unknown,
  context: TrustedToolContext
) {
  const input = orderReadInput.parse(raw);
  const order = await orders.findOne({
    id: input.order_id,
    actorId: context.actorId,
    tenantId: context.tenantId
  });

  if (!order) return { found: false };
  return {
    found: true,
    order_id: order.id,
    status: order.publicStatus,
    refundable: order.refundable
  };
}

Именно эта конструкция обезвреживает самую частую атаку на агентов.

Prompt injection не получает новых прав. Текст пользователя или RAG-фрагмент может просить игнорировать правила, изменить tenant или вызвать скрытую функцию. Но если handler не принимает эти поля из аргументов и проверяет ownership в запросе, такая инструкция остаётся просто текстом - она не становится разрешением.

Чтобы почувствовать, какой класс риска у tool и что он требует, прогоните его через классификатор.

Интерактивная лаборатория 2

Классификатор риска tool

Класс: read. Выполнение после server-side authorization.

Tools описаны и защищены по правам. Теперь свяжем их в цикл - и главное требование к нему в том, чтобы он был коротким и наблюдаемым.

Проверка знаний

Откуда берётся actor_id при вызове tool?

Ссылки