/** * Read-only Prisma proxy. * * Wraps a PrismaClient and blocks write operations at the application level. * Used to enforce read-only access for AI read-tools (EGAI 4.1.1.2 / IAAI 3.6.22). */ import type { prisma } from "@capakraken/db"; type PrismaClient = typeof prisma; const WRITE_METHODS = new Set([ "create", "createMany", "createManyAndReturn", "update", "updateMany", "upsert", "delete", "deleteMany", ]); // Client-level raw/escape hatches that MUST be blocked on a read-only // context. Missing any one of these lets a read-tool smuggle writes via // raw SQL, transactions, or the Mongo-style runCommandRaw. const BLOCKED_CLIENT_METHODS = new Set([ "$executeRaw", "$executeRawUnsafe", "$transaction", "$queryRawUnsafe", "$runCommandRaw", ]); function readOnlyModelProxy(model: Record, modelName: string): unknown { return new Proxy(model, { get(target, prop) { if (typeof prop === "string" && WRITE_METHODS.has(prop)) { return () => { throw new Error( `Write operation "${prop}" on "${modelName}" not permitted on read-only context`, ); }; } return Reflect.get(target, prop); }, }); } export function createReadOnlyProxy(client: PrismaClient): PrismaClient { return new Proxy(client, { get(target, prop) { const value = Reflect.get(target, prop); // If accessing a model delegate (object with findMany, etc.), wrap it if (value && typeof value === "object" && "findMany" in (value as Record)) { return readOnlyModelProxy(value as Record, String(prop)); } // Block raw/escape-hatch methods at the client level. $queryRaw // (template-tagged) is allowed — it's read-only by API contract; // $queryRawUnsafe is blocked because a crafted string could be // used to smuggle DDL/DML. if (typeof prop === "string" && BLOCKED_CLIENT_METHODS.has(prop)) { return () => { throw new Error( `Raw/escape operation "${String(prop)}" not permitted on read-only context`, ); }; } return value; }, }) as PrismaClient; }