From 7a472023bedf0dd6b86c81bae65b58a865dba87a Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 9 Jul 2026 20:13:49 +0000 Subject: [PATCH] Harden Clip public browse pagination Co-Authored-By: Paperclip --- server/src/__tests__/clips-routes.test.ts | 16 ++++++++++++++++ server/src/routes/clips.ts | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/clips-routes.test.ts b/server/src/__tests__/clips-routes.test.ts index c42d308042e..9dc5a59865f 100644 --- a/server/src/__tests__/clips-routes.test.ts +++ b/server/src/__tests__/clips-routes.test.ts @@ -5,6 +5,7 @@ import { clipRoutes } from "../routes/clips.js"; import { errorHandler } from "../middleware/index.js"; const mockClipService = vi.hoisted(() => ({ + listPublic: vi.fn(), getPublicDetail: vi.fn(), getPublicRevision: vi.fn(), getCreatorPublicProfile: vi.fn(), @@ -168,6 +169,21 @@ describe("clip routes", () => { vi.clearAllMocks(); }); + it("ignores non-finite public browse pagination params", async () => { + mockClipService.listPublic.mockResolvedValue([]); + + const res = await request(createApp()).get("/api/public/clips?limit=abc&offset=NaN"); + + expect(res.status).toBe(200); + expect(mockClipService.listPublic).toHaveBeenCalledWith({ + q: null, + type: null, + tag: null, + limit: undefined, + offset: undefined, + }); + }); + it("normalizes public publish moderation and trust states before persistence", async () => { const companyId = "11111111-1111-4111-8111-111111111111"; const clipId = "22222222-2222-4222-8222-222222222222"; diff --git a/server/src/routes/clips.ts b/server/src/routes/clips.ts index e102e8b829c..d92967dea58 100644 --- a/server/src/routes/clips.ts +++ b/server/src/routes/clips.ts @@ -260,8 +260,10 @@ export function clipRoutes(db: Db, storage?: StorageService) { const q = typeof req.query.q === "string" ? req.query.q : null; const type = typeof req.query.type === "string" ? req.query.type : null; const tag = typeof req.query.tag === "string" ? req.query.tag : null; - const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : undefined; - const offset = typeof req.query.offset === "string" ? Number(req.query.offset) : undefined; + const limitRaw = typeof req.query.limit === "string" ? Number(req.query.limit) : undefined; + const offsetRaw = typeof req.query.offset === "string" ? Number(req.query.offset) : undefined; + const limit = limitRaw !== undefined && Number.isFinite(limitRaw) ? limitRaw : undefined; + const offset = offsetRaw !== undefined && Number.isFinite(offsetRaw) ? offsetRaw : undefined; res.json(await svc.listPublic({ q, type, tag, limit, offset })); });