From 21d7b3b5dcbc3ca66052f7ad64e4cbec6cd19cfd Mon Sep 17 00:00:00 2001 From: w287346141 <57440615+w287346141@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:46:05 +0800 Subject: [PATCH] Validate dashboard checkpoint ids --- src/server/api/checkpoint-delete.ts | 6 ++-- src/server/api/checkpoint-restore.ts | 6 ++-- tests/dashboard-checkpoint-id.test.ts | 40 +++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 tests/dashboard-checkpoint-id.test.ts diff --git a/src/server/api/checkpoint-delete.ts b/src/server/api/checkpoint-delete.ts index 9fd987a..3b7f295 100644 --- a/src/server/api/checkpoint-delete.ts +++ b/src/server/api/checkpoint-delete.ts @@ -13,13 +13,15 @@ export async function handleCheckpointDelete( const rootDir = ctx.getCurrentCwd?.(); if (!rootDir) return { status: 400, body: { error: "no active workspace" } }; - let parsed: { id?: string }; + let parsed: { id?: unknown }; try { parsed = JSON.parse(body); } catch { return { status: 400, body: { error: "invalid JSON" } }; } - if (!parsed.id) return { status: 400, body: { error: "missing id" } }; + if (typeof parsed.id !== "string" || !parsed.id) { + return { status: 400, body: { error: "missing id" } }; + } const ok = deleteCheckpoint(rootDir, parsed.id); return ok diff --git a/src/server/api/checkpoint-restore.ts b/src/server/api/checkpoint-restore.ts index 51fce39..b90d36d 100644 --- a/src/server/api/checkpoint-restore.ts +++ b/src/server/api/checkpoint-restore.ts @@ -13,13 +13,15 @@ export async function handleCheckpointRestore( const rootDir = ctx.getCurrentCwd?.(); if (!rootDir) return { status: 400, body: { error: "no active workspace" } }; - let parsed: { id?: string }; + let parsed: { id?: unknown }; try { parsed = JSON.parse(body); } catch { return { status: 400, body: { error: "invalid JSON" } }; } - if (!parsed.id) return { status: 400, body: { error: "missing id" } }; + if (typeof parsed.id !== "string" || !parsed.id) { + return { status: 400, body: { error: "missing id" } }; + } const result: RestoreResult = restoreCheckpoint(rootDir, parsed.id); return { status: 200, body: result }; diff --git a/tests/dashboard-checkpoint-id.test.ts b/tests/dashboard-checkpoint-id.test.ts new file mode 100644 index 0000000..e93b757 --- /dev/null +++ b/tests/dashboard-checkpoint-id.test.ts @@ -0,0 +1,40 @@ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { handleCheckpointDelete } from "../src/server/api/checkpoint-delete.js"; +import { handleCheckpointRestore } from "../src/server/api/checkpoint-restore.js"; +import type { DashboardContext } from "../src/server/context.js"; + +let root: string; + +function ctx(): DashboardContext { + return { + mode: "standalone", + configPath: join(root, ".carboncode", "config.json"), + usageLogPath: join(root, ".carboncode", "usage.jsonl"), + getCurrentCwd: () => root, + }; +} + +beforeEach(() => { + root = mkdtempSync(join(tmpdir(), "carboncode-checkpoint-id-")); +}); + +afterEach(() => { + rmSync(root, { recursive: true, force: true }); +}); + +describe("dashboard checkpoint id validation", () => { + it("rejects non-string restore ids", async () => { + const result = await handleCheckpointRestore("POST", [], JSON.stringify({ id: 123 }), ctx()); + + expect(result).toEqual({ status: 400, body: { error: "missing id" } }); + }); + + it("rejects non-string delete ids", async () => { + const result = await handleCheckpointDelete("POST", [], JSON.stringify({ id: ["cp"] }), ctx()); + + expect(result).toEqual({ status: 400, body: { error: "missing id" } }); + }); +});