mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 02:44:50 +01:00
Harden public Clip revision reads
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
bf6d6ed6e3
commit
fed84a8547
2 files changed
+29
-5
No files matched your search
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -50,6 +50,10 @@ function first<T>(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 {
|
||||
|
||||
Reference in new issue
Block a user