Глава 17

Чтение заказа начинается с ownership check

Даже read-only tool раскрывает персональные данные, поэтому чтение заказа начинается не с запроса, а с проверки владения. Запрос к базе всегда включает actor и tenant из доверенной сессии, а ответ минимизируется до полей, которые агенту действительно нужны. Модель называет order_id; всё остальное решает сервер.

TypeScript
async function findPublicOrder(
  orderId: string,
  context: TrustedToolContext
) {
  const row = await db.order.findFirst({
    where: {
      id: orderId,
      customerId: context.actorId,
      tenantId: context.tenantId
    },
    select: {
      id: true,
      publicStatus: true,
      deliveredAt: true,
      currency: true,
      refundableSubtotal: true,
      version: true
    }
  });

  return row ? { found: true, order: row } : { found: false };
}

Две детали здесь спасают от утечек. Первая: несуществующий и чужой order id возвращают ОДИНАКОВЫЙ публичный результат { found: false } - иначе tool превращается в oracle для перебора чужих заказов. Вторая: даже если пользователь назвал email или телефон в чате, сопоставление identity выполняет ваш проверенный auth flow, а не модель по тексту.

Rate limit тоже должен учитывать actor и tenant, а не только IP.

  • Ограничьте число разных order ids за короткий период.
  • Записывайте denied lookups без чувствительного payload.
  • Не возвращайте внутренний fraud status или комментарии оператора.
  • Не позволяйте модели строить произвольные SQL, GraphQL или фильтры.

Заказ прочитан безопасно. Самое рискованное действие - возврат денег - мы намеренно не даём агенту целиком, а делим на три шага.

Ссылки