diff --git a/web/actions/projects.ts b/web/actions/projects.ts index 1f5592d..19f52a8 100644 --- a/web/actions/projects.ts +++ b/web/actions/projects.ts @@ -40,6 +40,18 @@ import cronstrue from "cronstrue"; import { startMigration } from "./migrations"; import { inngest } from "@/lib/inngest/client"; +function isValidImageReferencePart(reference: string): boolean { + // Allow only characters that are valid in Docker tags/digests and avoid path traversal. + // Tags: letters, digits, underscores, periods and dashes; Digests: "algorithm:hex". + // This regex is intentionally conservative. + const tagPattern = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/; + const digestPattern = /^[A-Za-z0-9_+.-]+:[0-9a-fA-F]{32,256}$/; + + return reference === "latest" || + tagPattern.test(reference) || + digestPattern.test(reference); +} + function parseImageReference(image: string): { registry: string; namespace: string; @@ -96,6 +108,13 @@ export async function validateDockerImage( parseImageReference(image); const reference = digest || tag || "latest"; + if (!isValidImageReferencePart(reference)) { + return { + valid: false, + error: "Invalid image tag or digest", + }; + } + if (registry === "docker.io") { const repoPath = namespace === "library" ? repository : `${namespace}/${repository}`;