From f13df7a145309d1bc013f5cd9229a2f536b6c496 Mon Sep 17 00:00:00 2001 From: Sravanjangam <163002695+Sravanjangam@users.noreply.github.com> Date: Sun, 23 Aug 2026 03:24:03 +0530 Subject: [PATCH] fix(validation): bound documents-with-memories limit and bulk-delete containerTags - DocumentsWithMemoriesQuerySchema.limit gains .max(100), matching the cap style of every sibling list schema (SearchRequest <= 100, ListMemories <= 1100). The omission was unguarded: each row expands joined memory entries, so an arbitrary limit was a memory/CPU/cost amplifier. Cap test added, mirroring the existing ListMemories cap test. - BulkDeleteMemoriesSchema.containerTags now caps at 100 tags of <=256 chars, aligning the most destructive operation in the schema with the hard .max(100) already applied to its ids array. --- packages/validation/api.test.ts | 19 +++++++++++++++++++ packages/validation/api.ts | 15 +++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/validation/api.test.ts b/packages/validation/api.test.ts index e186af88f..959974cb6 100644 --- a/packages/validation/api.test.ts +++ b/packages/validation/api.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "bun:test" import { readFileSync } from "node:fs" import { + BulkDeleteMemoriesSchema, DocumentsWithMemoriesQuerySchema, ListMemoriesQuerySchema, SearchRequestSchema, @@ -151,4 +152,22 @@ describe("pagination query schemas", () => { expect(parsed.page).toBe(2) expect(parsed.limit).toBe(50) }) + + it("DocumentsWithMemoriesQuerySchema caps limit at 100", () => { + expect(DocumentsWithMemoriesQuerySchema.safeParse({ limit: 101 }).success) + .toBe(false) + }) + + it("BulkDeleteMemoriesSchema caps containerTags at 100 entries of bounded length", () => { + const tooMany = { + containerTags: Array.from({ length: 101 }, (_, i) => `tag_${i}`), + } + expect(BulkDeleteMemoriesSchema.safeParse(tooMany).success).toBe(false) + + const tagTooLong = { containerTags: ["x".repeat(257)] } + expect(BulkDeleteMemoriesSchema.safeParse(tagTooLong).success).toBe(false) + + const ok = { containerTags: ["tag_a", "tag_b"] } + expect(BulkDeleteMemoriesSchema.safeParse(ok).success).toBe(true) + }) }) diff --git a/packages/validation/api.ts b/packages/validation/api.ts index f066bfcd4..f1700c6bb 100644 --- a/packages/validation/api.ts +++ b/packages/validation/api.ts @@ -1102,8 +1102,11 @@ export const DocumentsWithMemoriesQuerySchema = z description: "Page number to fetch", example: 1, }), - limit: z.number().int().min(1).default(10).openapi({ - description: "Number of items per page", + // Capped like every sibling list schema (SearchRequest <= 100, + // ListMemories <= 1100): each row expands joined memoryEntries, so an + // unbounded limit here is a memory/CPU/cost amplifier. + limit: z.number().int().min(1).max(100).default(10).openapi({ + description: "Number of items per page (max 100)", example: 10, }), sort: z.enum(["createdAt", "updatedAt"]).default("createdAt").openapi({ @@ -1408,13 +1411,17 @@ export const BulkDeleteMemoriesSchema = z description: "Array of memory IDs to delete (max 100 at once)", example: ["acxV5LHMEsG2hMSNb4umbn", "bxcV5LHMEsG2hMSNb4umbn"], }), + // Bounded like the ids array above: this is the most destructive + // operation in the schema ("delete ALL memories in these containers"), + // so the tag list must not be an unbounded fan-out vector. containerTags: z - .array(z.string()) + .array(z.string().max(256)) .min(1) + .max(100) .optional() .openapi({ description: - "Array of container tags - all memories in these containers will be deleted", + "Array of container tags - all memories in these containers will be deleted (max 100 at once)", example: ["user_123", "project_123"], }), })