From 547ba2d04bc66f820e3635826a85d6141a233b7e Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sat, 9 May 2026 01:09:50 -0600 Subject: [PATCH] feat(validation): enhance registry URL validation in schema - Introduced a new validation schema for registry URLs, ensuring they conform to a specific format (hostname[:port]) and disallow shell metacharacters. - Updated the `createSchema`, `apiCreateRegistry`, and `apiTestRegistry` functions to utilize the new registry URL validation. - Improved security and input integrity for registry URL fields. - Updated the `removeRegistry` function to escape the registry URL during logout to prevent command injection vulnerabilities. --- packages/server/src/db/schema/registry.ts | 17 ++++++++++++++--- packages/server/src/services/registry.ts | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/server/src/db/schema/registry.ts b/packages/server/src/db/schema/registry.ts index ee9ca662a..68db88f80 100644 --- a/packages/server/src/db/schema/registry.ts +++ b/packages/server/src/db/schema/registry.ts @@ -44,11 +44,22 @@ export const registryRelations = relations(registry, ({ many }) => ({ }), })); +// Registry URLs must be hostname[:port] only — no shell metacharacters +// Empty string is allowed (means default/Docker Hub registry) +const registryUrlSchema = z + .string() + .refine( + (val) => + val === "" || + /^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?(:\d{1,5})?$/.test(val), + "Registry URL must be a valid hostname or hostname:port (e.g. registry.example.com or localhost:5000)", + ); + const createSchema = createInsertSchema(registry, { registryName: z.string().min(1), username: z.string().min(1), password: z.string().min(1), - registryUrl: z.string(), + registryUrl: registryUrlSchema, organizationId: z.string().min(1), registryId: z.string().min(1), registryType: z.enum(["cloud"]), @@ -61,7 +72,7 @@ export const apiCreateRegistry = createSchema registryName: z.string().min(1), username: z.string().min(1), password: z.string().min(1), - registryUrl: z.string(), + registryUrl: registryUrlSchema, registryType: z.enum(["cloud"]), imagePrefix: z.string().nullable().optional(), }) @@ -74,7 +85,7 @@ export const apiTestRegistry = createSchema.pick({}).extend({ registryName: z.string().optional(), username: z.string().min(1), password: z.string().min(1), - registryUrl: z.string(), + registryUrl: registryUrlSchema, registryType: z.enum(["cloud"]), imagePrefix: z.string().nullable().optional(), serverId: z.string().optional(), diff --git a/packages/server/src/services/registry.ts b/packages/server/src/services/registry.ts index ad18e0b66..65ba80921 100644 --- a/packages/server/src/services/registry.ts +++ b/packages/server/src/services/registry.ts @@ -85,7 +85,7 @@ export const removeRegistry = async (registryId: string) => { } if (!IS_CLOUD) { - await execAsync(`docker logout ${response.registryUrl}`); + await execAsync(`docker logout ${shEscape(response.registryUrl)}`); } return response;