From 6943a395fe1bac2ce4fa5ce5d5e5303b8d91cf19 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 20 Jul 2026 17:07:32 -0600 Subject: [PATCH] fix(security): stop user-supplied schedule appName and escape its shell paths drizzle-zod exposed appName (notNull + $defaultFn) as optional in the insert schema, letting callers override it; it then flowed unquoted into rm -rf/mkdir paths. Omit appName from createScheduleSchema and quote() the schedule fullPath sinks (completes GHSA-fgh8 VULN-015 alongside the mount/patch fixes). --- packages/server/src/db/schema/schedule.ts | 2 +- packages/server/src/services/schedule.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/server/src/db/schema/schedule.ts b/packages/server/src/db/schema/schedule.ts index 55c831f54..7a48cccff 100644 --- a/packages/server/src/db/schema/schedule.ts +++ b/packages/server/src/db/schema/schedule.ts @@ -81,7 +81,7 @@ export const schedulesRelations = relations(schedules, ({ one, many }) => ({ export const createScheduleSchema = createInsertSchema(schedules, { scheduleType: z.enum(["application", "compose", "server", "dokploy-server"]), -}); +}).omit({ appName: true }); export const updateScheduleSchema = createScheduleSchema.extend({ scheduleId: z.string().min(1), diff --git a/packages/server/src/services/schedule.ts b/packages/server/src/services/schedule.ts index c94973e02..fd01ac0bc 100644 --- a/packages/server/src/services/schedule.ts +++ b/packages/server/src/services/schedule.ts @@ -1,6 +1,7 @@ import path from "node:path"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; +import { quote } from "shell-quote"; import type { z } from "zod"; import { IS_CLOUD, paths } from "../constants"; import { db } from "../db"; @@ -142,7 +143,7 @@ export const deleteSchedule = async (scheduleId: string) => { const { SCHEDULES_PATH } = paths(!!serverId); const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || ""); - const command = `rm -rf ${fullPath}`; + const command = `rm -rf ${quote([fullPath])}`; if (serverId) { await execAsyncRemote(serverId, command); } else { @@ -198,12 +199,13 @@ const handleScript = async (schedule: Schedule) => { ${schedule?.script || ""}`; const encodedContent = encodeBase64(scriptWithPid); + const scriptPath = `${fullPath}/script.sh`; const script = ` - mkdir -p ${fullPath} - rm -f ${fullPath}/script.sh - touch ${fullPath}/script.sh - chmod +x ${fullPath}/script.sh - echo "${encodedContent}" | base64 -d > ${fullPath}/script.sh + mkdir -p ${quote([fullPath])} + rm -f ${quote([scriptPath])} + touch ${quote([scriptPath])} + chmod +x ${quote([scriptPath])} + echo "${encodedContent}" | base64 -d > ${quote([scriptPath])} `; if (schedule?.scheduleType === "dokploy-server") {