fix(web): harden config PATCH allowlists against prototype-chain keys
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -87,9 +87,10 @@ describe("config DELETE", () => {
|
|||||||
const before = await prisma.config.count();
|
const before = await prisma.config.count();
|
||||||
expect(before).toBeGreaterThan(0);
|
expect(before).toBeGreaterThan(0);
|
||||||
|
|
||||||
const res = await DELETE(delReq("__proto__"));
|
for (const bad of ["__proto__", "constructor"]) {
|
||||||
|
const res = await DELETE(delReq(bad));
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
|
}
|
||||||
// Must not have collapsed to deleteMany({ where: {} }) and wiped the table.
|
// Must not have collapsed to deleteMany({ where: {} }) and wiped the table.
|
||||||
expect(await prisma.config.count()).toBe(before);
|
expect(await prisma.config.count()).toBe(before);
|
||||||
expect(await prisma.config.findUnique({ where: { key: "qobuz.password" } })).not.toBeNull();
|
expect(await prisma.config.findUnique({ where: { key: "qobuz.password" } })).not.toBeNull();
|
||||||
|
|||||||
@@ -26,4 +26,11 @@ describe("discover config API", () => {
|
|||||||
expect(body["discover.chunkSize"]).toBe("25");
|
expect(body["discover.chunkSize"]).toBe("25");
|
||||||
expect(await prisma.config.findUnique({ where: { key: "nope" } })).toBeNull();
|
expect(await prisma.config.findUnique({ where: { key: "nope" } })).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ignores prototype-chain keys (__proto__, constructor) and writes no Config row", async () => {
|
||||||
|
const before = await prisma.config.count();
|
||||||
|
const res = await patch({ __proto__: "x", constructor: "y" });
|
||||||
|
expect((await res.json()).updated).toBe(0);
|
||||||
|
expect(await prisma.config.count()).toBe(before);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export async function PATCH(request: Request) {
|
|||||||
return Response.json({ error: "invalid JSON" }, { status: 400 });
|
return Response.json({ error: "invalid JSON" }, { status: 400 });
|
||||||
}
|
}
|
||||||
const entries = Object.entries((body ?? {}) as Record<string, unknown>).filter(
|
const entries = Object.entries((body ?? {}) as Record<string, unknown>).filter(
|
||||||
([k]) => k in DEFAULTS,
|
([k]) => Object.prototype.hasOwnProperty.call(DEFAULTS, k),
|
||||||
);
|
);
|
||||||
for (const [key, value] of entries) {
|
for (const [key, value] of entries) {
|
||||||
await prisma.config.upsert({
|
await prisma.config.upsert({
|
||||||
|
|||||||
@@ -26,4 +26,11 @@ describe("monitor config API", () => {
|
|||||||
expect(body["monitor.pollIntervalHours"]).toBe("12");
|
expect(body["monitor.pollIntervalHours"]).toBe("12");
|
||||||
expect(await prisma.config.findUnique({ where: { key: "nope" } })).toBeNull();
|
expect(await prisma.config.findUnique({ where: { key: "nope" } })).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ignores prototype-chain keys (__proto__, constructor) and writes no Config row", async () => {
|
||||||
|
const before = await prisma.config.count();
|
||||||
|
const res = await patch({ __proto__: "x", constructor: "y" });
|
||||||
|
expect((await res.json()).updated).toBe(0);
|
||||||
|
expect(await prisma.config.count()).toBe(before);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export async function PATCH(request: Request) {
|
|||||||
return Response.json({ error: "invalid JSON" }, { status: 400 });
|
return Response.json({ error: "invalid JSON" }, { status: 400 });
|
||||||
}
|
}
|
||||||
const entries = Object.entries((body ?? {}) as Record<string, unknown>).filter(
|
const entries = Object.entries((body ?? {}) as Record<string, unknown>).filter(
|
||||||
([k]) => k in DEFAULTS,
|
([k]) => Object.prototype.hasOwnProperty.call(DEFAULTS, k),
|
||||||
);
|
);
|
||||||
for (const [key, value] of entries) {
|
for (const [key, value] of entries) {
|
||||||
await prisma.config.upsert({
|
await prisma.config.upsert({
|
||||||
|
|||||||
Reference in New Issue
Block a user