mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 13:20:54 +01:00
Fix Clip dependency and path validation gaps
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
9bb1d3bb24
commit
f38235b51b
4 files changed
+40
-2
No files matched your search
@@ -292,6 +292,10 @@ describe("clip manifest foundation", () => {
|
||||
expect(first.dependencies.adapters.map((entry) => entry.type)).toEqual(["claude_local", "codex_local"]);
|
||||
expect(first.dependencies.skills.map((entry) => entry.key)).toEqual(["triage"]);
|
||||
expect(first.dependencies.secrets.map((entry) => entry.key)).toContain("GH_TOKEN");
|
||||
expect(first.dependencies.secrets.find((entry) => entry.key === "GH_TOKEN")).toMatchObject({
|
||||
requirement: "required",
|
||||
sourceRefs: ["agents.support", "envInputs.0"],
|
||||
});
|
||||
expect(first.dependencies.permissions.map((entry) => entry.capability)).toContain("github.issues.write");
|
||||
expect(first.dependencies.runtime).toMatchObject({
|
||||
browser: true,
|
||||
|
||||
@@ -894,6 +894,11 @@ function addMapEntry<T extends { sourceRefs: Set<string> }>(map: Map<string, T>,
|
||||
map.set(key, value);
|
||||
return;
|
||||
}
|
||||
const existingRequirement = existing as T & { requirement?: "required" | "optional" };
|
||||
const nextRequirement = (value as T & { requirement?: "required" | "optional" }).requirement;
|
||||
if (existingRequirement.requirement === "optional" && nextRequirement === "required") {
|
||||
existingRequirement.requirement = "required";
|
||||
}
|
||||
for (const sourceRef of value.sourceRefs) {
|
||||
existing.sourceRefs.add(sourceRef);
|
||||
}
|
||||
|
||||
@@ -232,6 +232,25 @@ describe("clip routes", () => {
|
||||
expect(mockCompanyPortabilityService.previewExport).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects malformed clipId path params before clip database lookups", async () => {
|
||||
const revisionRes = await request(createApp())
|
||||
.post("/api/clips/not-a-uuid/revisions")
|
||||
.send({
|
||||
manifestChecksum: "sha256:manifest-v2",
|
||||
artifactChecksum: "sha256:artifact-v2",
|
||||
manifestPayload: { schema: "paperclip.clip/v1" },
|
||||
});
|
||||
const patchRes = await request(createApp())
|
||||
.patch("/api/clips/not-a-uuid")
|
||||
.send({ title: "Updated title" });
|
||||
|
||||
expect(revisionRes.status).toBe(400);
|
||||
expect(revisionRes.body).toEqual({ error: "Invalid clipId path parameter." });
|
||||
expect(patchRes.status).toBe(400);
|
||||
expect(patchRes.body).toEqual({ error: "Invalid clipId path parameter." });
|
||||
expect(mockClipService.getClipById).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("strips owner-supplied trust states when creating revisions", async () => {
|
||||
const clipId = "33333333-3333-4333-8333-333333333333";
|
||||
const sourceCompanyId = "22222222-2222-4222-8222-222222222222";
|
||||
|
||||
@@ -75,6 +75,14 @@ function getClipCompanyIdParam(req: Request) {
|
||||
return companyId;
|
||||
}
|
||||
|
||||
function getClipIdParam(req: Request) {
|
||||
const clipId = req.params.clipId as string;
|
||||
if (!isUuidLike(clipId)) {
|
||||
throw badRequest("Invalid clipId path parameter.");
|
||||
}
|
||||
return clipId;
|
||||
}
|
||||
|
||||
function evictExpiredClipRateLimitHits(cutoff: number, now: number) {
|
||||
if (now - clipRateLimitLastEvictionAt < CLIP_RATE_LIMIT_EVICTION_INTERVAL_MS) return;
|
||||
clipRateLimitLastEvictionAt = now;
|
||||
@@ -607,7 +615,8 @@ export function clipRoutes(db: Db, storage?: StorageService) {
|
||||
});
|
||||
|
||||
router.post("/clips/:clipId/revisions", validate(createClipRevisionSchema), async (req, res) => {
|
||||
const clip = await svc.getClipById(req.params.clipId as string);
|
||||
const clipId = getClipIdParam(req);
|
||||
const clip = await svc.getClipById(clipId);
|
||||
if (!clip) {
|
||||
res.status(404).json({ error: "Clip not found" });
|
||||
return;
|
||||
@@ -640,7 +649,8 @@ export function clipRoutes(db: Db, storage?: StorageService) {
|
||||
});
|
||||
|
||||
router.patch("/clips/:clipId", validate(updateClipSchema), async (req, res) => {
|
||||
const clip = await svc.getClipById(req.params.clipId as string);
|
||||
const clipId = getClipIdParam(req);
|
||||
const clip = await svc.getClipById(clipId);
|
||||
if (!clip) {
|
||||
res.status(404).json({ error: "Clip not found" });
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user