From fed84a8547f5572e5e08cb07415180cc4322007e Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 9 Jul 2026 18:47:25 +0000 Subject: [PATCH] Harden public Clip revision reads Co-Authored-By: Paperclip --- server/src/__tests__/clips-service.test.ts | 24 ++++++++++++++++++++-- server/src/services/clips.ts | 10 ++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/server/src/__tests__/clips-service.test.ts b/server/src/__tests__/clips-service.test.ts index 4a1b5445051..a4cfe46f7a1 100644 --- a/server/src/__tests__/clips-service.test.ts +++ b/server/src/__tests__/clips-service.test.ts @@ -212,7 +212,7 @@ describeEmbeddedPostgres("clipService", () => { expect(revisions.map((revision) => revision.revisionNumber)).toEqual([1, 2, 3]); }); - it("queries creator profile clips by creator before applying limits", async () => { + it("queries creator profile clips by creator before applying limits without exposing unlisted clips", async () => { const targetCompanyId = await seedCompany(); const otherCompanyId = await seedCompany(); const [targetProfile] = await db.insert(clipCreatorProfiles).values({ @@ -269,7 +269,27 @@ describeEmbeddedPostgres("clipService", () => { expect(profile?.clips.map((clip) => clip.slug).sort()).toEqual([ "target-public-" + targetCompanyId.slice(0, 8), - "target-unlisted-" + targetCompanyId.slice(0, 8), ]); }); + + it("does not serve blocked or failed revisions through public revision reads", async () => { + const companyId = await seedCompany(); + const { clip } = await publishFixture(companyId); + + const blocked = await svc.createRevision(clip.id, { + manifestChecksum: "sha256:manifest-blocked", + artifactChecksum: "sha256:artifact-blocked", + manifestPayload: { schema: "paperclip.clip/v1", revision: "blocked" }, + securityReviewState: "blocked", + }); + const failed = await svc.createRevision(clip.id, { + manifestChecksum: "sha256:manifest-failed", + artifactChecksum: "sha256:artifact-failed", + manifestPayload: { schema: "paperclip.clip/v1", revision: "failed" }, + verificationState: "failed", + }); + + expect(await svc.getPublicRevision(clip.slug, blocked.revision.revisionNumber)).toBeNull(); + expect(await svc.getPublicRevision(clip.slug, failed.revision.revisionNumber)).toBeNull(); + }); }); diff --git a/server/src/services/clips.ts b/server/src/services/clips.ts index e918c30515d..6f4a1fcdddb 100644 --- a/server/src/services/clips.ts +++ b/server/src/services/clips.ts @@ -50,6 +50,10 @@ function first(rows: T[]): T | null { return rows[0] ?? null; } +function isPubliclyReadableRevision(revision: typeof clipRevisions.$inferSelect) { + return revision.securityReviewState !== "blocked" && revision.verificationState !== "failed"; +} + function publicMetrics(row: typeof clips.$inferSelect) { return { importCount: row.importCount, @@ -150,7 +154,8 @@ export function clipService(db: Db) { async function getPublicCurrentRevision(clip: typeof clips.$inferSelect) { const revisionId = clip.latestApprovedRevisionId ?? clip.currentRevisionId; if (!revisionId) return null; - return first(await db.select().from(clipRevisions).where(eq(clipRevisions.id, revisionId)).limit(1)); + const revision = first(await db.select().from(clipRevisions).where(eq(clipRevisions.id, revisionId)).limit(1)); + return revision && isPubliclyReadableRevision(revision) ? revision : null; } async function getPublicClipBySlug(slug: string) { @@ -230,7 +235,7 @@ export function clipService(db: Db) { const row = await getPublicClipBySlug(slug); if (!row) return null; const revision = await getRevisionByNumber(row.clip.id, revisionNumber); - if (!revision) return null; + if (!revision || !isPubliclyReadableRevision(revision)) return null; const dependencies = await db.select().from(clipDependencies).where(eq(clipDependencies.revisionId, revision.id)); return serializePublicClip({ clip: row.clip, @@ -678,7 +683,6 @@ export function clipService(db: Db) { if (!profile) return null; const publicClips = await listPublic({ creatorProfileId: profile.id, - includeUnlisted: true, limit: 100, }); return {