diff --git a/src/web/public/index.html b/src/web/public/index.html index b8599ba1..4dc12f27 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -1393,7 +1393,7 @@
- Drag or use arrows to reorder. Changes are saved automatically. + Use arrows to reorder. Changes are saved automatically.
diff --git a/src/web/routes/case-routes.ts b/src/web/routes/case-routes.ts index 6b825164..b3508ddc 100644 --- a/src/web/routes/case-routes.ts +++ b/src/web/routes/case-routes.ts @@ -19,6 +19,7 @@ import { SseEvent } from '../sse-events.js'; import type { EventPort, ConfigPort } from '../ports/index.js'; const LINKED_CASES_FILE = join(homedir(), '.codeman', 'linked-cases.json'); +const SAFE_CASE_NAME = /^[a-zA-Z0-9_-]+$/; /** Read and parse linked-cases.json, returning empty object on missing/invalid file. */ async function readLinkedCases(): Promise> { @@ -46,7 +47,7 @@ export function registerCaseRoutes(app: FastifyInstance, ctx: EventPort & Config try { const entries = await fs.readdir(CASES_DIR, { withFileTypes: true }); for (const e of entries) { - if (e.isDirectory()) { + if (e.isDirectory() && SAFE_CASE_NAME.test(e.name)) { cases.push({ name: e.name, path: join(CASES_DIR, e.name), @@ -62,7 +63,7 @@ export function registerCaseRoutes(app: FastifyInstance, ctx: EventPort & Config const linkedCases = await readLinkedCases(); const existingNames = new Set(cases.map((c) => c.name)); for (const [name, path] of Object.entries(linkedCases)) { - if (!existingNames.has(name) && existsSync(path)) { + if (!existingNames.has(name) && SAFE_CASE_NAME.test(name) && existsSync(path)) { cases.push({ name, path,