100% spot on. Relying on the LLM to supply or echo tenantId was a textbook design flaw. We completely overhauled this in v1.7.0 (docs/security/anti-idor.md): No tenantId in Zod Schema: Tools now only expose business arguments (invoiceId). Exposing tenantId to the model triggers a security warning. Server-Side Session Context: ctx.tenantId is strictly injected from the authenticated session and bound to DB queries (where: { id, tenantId: ctx.tenantId }). Post-Fetch Assertion (assertTenant): Runs after execute() and before llmDto. If record.tenantId !== ctx.tenantId, it fails closed and throws ToolAccessDeniedError. import { z } from "zod"; import { createTenantTool } from "avantgate/agent"; export const getInvoiceTool = createTenantTool({ id: "get_invoice", parameters: z.object({ invoiceId: z.string() }), // LLM never touches tenantId roles: ["billing"], // Native RBAC guard assertTenant: (record, ctx) => record.tenantId === ctx.tenantId, // Post-fetch defense execute: async ({ invoiceId }, ctx) => { return await db.invoices.findUnique({ where: { id: invoiceId, tenantId: ctx.tenantId }, }); }, llmDto: (inv) => ({ id: inv.id, total: inv.total }), }); Thanks for the sharp critique — it directly shaped our v1.7.0 multi-tenant hardening!
