From e4e8b33e9fadd5092ae7345bdc0715931f186143 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 21 May 2026 12:14:52 -0700 Subject: [PATCH] Enforce absolute paths for sudo commands --- src/components/RoleForm.tsx | 66 ++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/src/components/RoleForm.tsx b/src/components/RoleForm.tsx index 8fe2c55e9..2194344b7 100644 --- a/src/components/RoleForm.tsx +++ b/src/components/RoleForm.tsx @@ -46,6 +46,20 @@ function toSshSudoMode(value: string | null | undefined): SshSudoMode { return "none"; } +function hasOnlyAbsoluteSudoCommands(value: string | undefined): boolean { + if (!value?.trim()) return true; + + const commands = value + .split(",") + .map((command) => command.trim()) + .filter(Boolean); + + return commands.every((command) => { + const executable = command.split(/\s+/)[0]; + return executable.startsWith("/"); + }); +} + export type RoleFormValues = { name: string; description?: string; @@ -74,19 +88,33 @@ export function RoleForm({ const { isPaidUser } = usePaidStatus(); const { env } = useEnvContext(); - const formSchema = z.object({ - name: z - .string({ message: t("nameRequired") }) - .min(1) - .max(32), - description: z.string().max(255).optional(), - requireDeviceApproval: z.boolean().optional(), - allowSsh: z.boolean().optional(), - sshSudoMode: z.enum(SSH_SUDO_MODE_VALUES), - sshSudoCommands: z.string().optional(), - sshCreateHomeDir: z.boolean().optional(), - sshUnixGroups: z.string().optional() - }); + const formSchema = z + .object({ + name: z + .string({ message: t("nameRequired") }) + .min(1) + .max(32), + description: z.string().max(255).optional(), + requireDeviceApproval: z.boolean().optional(), + allowSsh: z.boolean().optional(), + sshSudoMode: z.enum(SSH_SUDO_MODE_VALUES), + sshSudoCommands: z.string().optional(), + sshCreateHomeDir: z.boolean().optional(), + sshUnixGroups: z.string().optional() + }) + .superRefine((values, ctx) => { + if ( + values.sshSudoMode === "commands" && + !hasOnlyAbsoluteSudoCommands(values.sshSudoCommands) + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["sshSudoCommands"], + message: + "Each sudo command must start with an absolute path (for example, /usr/bin/systemctl)." + }); + } + }); const defaultValues: RoleFormValues = role ? { @@ -296,7 +324,9 @@ export function RoleForm({ control={form.control} name="allowSsh" render={({ field }) => { - const allowSshOptions: OptionSelectOption<"allow" | "disallow">[] = [ + const allowSshOptions: OptionSelectOption< + "allow" | "disallow" + >[] = [ { value: "allow", label: t("roleAllowSshAllow") @@ -311,7 +341,9 @@ export function RoleForm({ {t("roleAllowSsh")} - + options={allowSshOptions} value={ sshDisabled @@ -322,7 +354,9 @@ export function RoleForm({ } onChange={(v) => { if (sshDisabled) return; - field.onChange(v === "allow"); + field.onChange( + v === "allow" + ); }} cols={2} disabled={sshDisabled}