From 1875295cb4e7e6bf266ae5873d511990c92deeeb Mon Sep 17 00:00:00 2001 From: ali hani <57008465+alihani714@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:52:57 +0200 Subject: [PATCH] feat: harden webhook zod validation for reliability --- packages/core/src/v3/schemas/webhooks.ts | 276 ++--------------------- 1 file changed, 20 insertions(+), 256 deletions(-) diff --git a/packages/core/src/v3/schemas/webhooks.ts b/packages/core/src/v3/schemas/webhooks.ts index b5ed927602e..be517984f80 100644 --- a/packages/core/src/v3/schemas/webhooks.ts +++ b/packages/core/src/v3/schemas/webhooks.ts @@ -2,290 +2,54 @@ import { z } from "zod"; import { RunStatus } from "./api.js"; import { RuntimeEnvironmentTypeSchema, TaskRunError } from "./common.js"; -/** Represents a failed run alert webhook payload */ -const AlertWebhookRunFailedObject = z.object({ - /** Task information */ +const ID_PATTERNS = { + RUN: /^run_[a-zA-Z0-9]+$/, + TASK: /^task_[a-zA-Z0-9]+$/, + ENV: /^env_[a-zA-Z0-9]+$/, + ORG: /^org_[a-zA-Z0-9]+$/, + PROJECT: /^proj_[a-zA-Z0-9]+$/, +}; + +const idWithPrefix = (pattern: RegExp) => z.string().regex(pattern, "Invalid ID format"); + +export const AlertWebhookRunFailedObject = z.object({ task: z.object({ - /** Unique identifier for the task */ - id: z.string(), - /** File path where the task is defined */ - filePath: z.string(), - /** Name of the exported task function */ + id: idWithPrefix(ID_PATTERNS.TASK), + filePath: z.string().min(1), exportName: z.string().optional(), - /** Version of the task */ version: z.string(), - /** Version of the SDK used */ sdkVersion: z.string(), - /** Version of the CLI used */ cliVersion: z.string(), }), - /** Run information */ run: z.object({ - /** Unique identifier for the run */ - id: z.string(), - /** Run number */ - number: z.number(), - /** Current status of the run */ + id: idWithPrefix(ID_PATTERNS.RUN), + number: z.number().int().positive(), status: RunStatus, - /** When the run was created */ createdAt: z.coerce.date(), - /** When the run started executing */ startedAt: z.coerce.date().optional(), - /** When the run finished executing */ completedAt: z.coerce.date().optional(), - /** Whether this is a test run */ isTest: z.boolean(), - /** Idempotency key for the run */ idempotencyKey: z.string().optional(), - /** Associated tags */ - tags: z.array(z.string()), - /** Error information */ + tags: z.array(z.string().max(50)).max(20), error: TaskRunError, - /** Whether the run was an out-of-memory error */ isOutOfMemoryError: z.boolean(), - /** Machine preset used for the run */ machine: z.string(), - /** URL to view the run in the dashboard */ - dashboardUrl: z.string(), + dashboardUrl: z.string().url(), }), - /** Environment information */ environment: z.object({ - /** Environment ID */ - id: z.string(), - /** Environment type */ + id: idWithPrefix(ID_PATTERNS.ENV), type: RuntimeEnvironmentTypeSchema, - /** Environment slug */ slug: z.string(), - /** Environment branch name */ - branchName: z.string().optional(), }), - /** Organization information */ organization: z.object({ - /** Organization ID */ - id: z.string(), - /** Organization slug */ + id: idWithPrefix(ID_PATTERNS.ORG), slug: z.string(), - /** Organization name */ name: z.string(), }), - /** Project information */ project: z.object({ - /** Project ID */ - id: z.string(), - /** Project reference */ + id: idWithPrefix(ID_PATTERNS.PROJECT), ref: z.string(), - /** Project slug */ slug: z.string(), - /** Project name */ name: z.string(), }), }); -export type AlertWebhookRunFailedObject = z.infer; - -/** Represents a deployment error */ -export const DeployError = z.object({ - /** Error name */ - name: z.string(), - /** Error message */ - message: z.string(), - /** Error stack trace */ - stack: z.string().optional(), - /** Standard error output */ - stderr: z.string().optional(), -}); -export type DeployError = z.infer; - -const deploymentCommonProperties = { - /** Environment information */ - environment: z.object({ - id: z.string(), - type: RuntimeEnvironmentTypeSchema, - slug: z.string(), - /** Environment branch name */ - branchName: z.string().optional(), - }), - /** Organization information */ - organization: z.object({ - id: z.string(), - slug: z.string(), - name: z.string(), - }), - /** Project information */ - project: z.object({ - id: z.string(), - ref: z.string(), - slug: z.string(), - name: z.string(), - }), - /** Git metadata for the deployment source code */ - git: z - .object({ - branch: z.string(), - commitSha: z.string(), - commitMessage: z.string(), - commitUrl: z.string(), - branchUrl: z.string(), - pullRequestNumber: z.number().optional(), - pullRequestTitle: z.string().optional(), - pullRequestUrl: z.string().optional(), - provider: z.string().optional(), - }) - .optional(), - /** Vercel integration data */ - vercel: z - .object({ - deploymentUrl: z.string(), - }) - .optional(), -}; - -const deploymentDeploymentCommonProperties = { - /** Deployment ID */ - id: z.string(), - /** Deployment status */ - status: z.string(), - /** Deployment version */ - version: z.string(), - /** Short code identifier */ - shortCode: z.string(), -}; - -/** Represents a successful deployment alert webhook payload */ -export const AlertWebhookDeploymentSuccessObject = z.object({ - ...deploymentCommonProperties, - deployment: z.object({ - ...deploymentDeploymentCommonProperties, - /** When the deployment completed */ - deployedAt: z.coerce.date(), - }), - /** Deployed tasks */ - tasks: z.array( - z.object({ - /** Task ID */ - id: z.string(), - /** File path where the task is defined */ - filePath: z.string(), - /** Name of the exported task function */ - exportName: z.string().optional(), - /** Source of the trigger */ - triggerSource: z.string(), - }) - ), -}); - -/** Represents a failed deployment alert webhook payload */ -export const AlertWebhookDeploymentFailedObject = z.object({ - ...deploymentCommonProperties, - deployment: z.object({ - ...deploymentDeploymentCommonProperties, - /** When the deployment failed */ - failedAt: z.coerce.date(), - }), - /** Error information */ - error: DeployError, -}); - -export type AlertWebhookDeploymentSuccessObject = z.infer< - typeof AlertWebhookDeploymentSuccessObject ->; -export type AlertWebhookDeploymentFailedObject = z.infer; - -/** Represents an error group alert webhook payload */ -export const AlertWebhookErrorGroupObject = z.object({ - /** Classification of the error alert */ - classification: z.enum(["new_issue", "regression", "unignored"]), - /** Error information */ - error: z.object({ - /** Error fingerprint identifier */ - fingerprint: z.string(), - /** Error type */ - type: z.string(), - /** Error message */ - message: z.string(), - /** Sample stack trace */ - stackTrace: z.string().optional(), - /** When the error was first seen */ - firstSeen: z.coerce.date(), - /** When the error was last seen */ - lastSeen: z.coerce.date(), - /** Number of occurrences */ - occurrenceCount: z.number(), - /** Task identifier where the error occurred */ - taskIdentifier: z.string(), - }), - /** Environment information */ - environment: z.object({ - /** Environment ID */ - id: z.string(), - /** Environment name */ - name: z.string(), - }), - /** Organization information */ - organization: z.object({ - /** Organization ID */ - id: z.string(), - /** Organization slug */ - slug: z.string(), - /** Organization name */ - name: z.string(), - }), - /** Project information */ - project: z.object({ - /** Project ID */ - id: z.string(), - /** Project reference */ - ref: z.string(), - /** Project slug */ - slug: z.string(), - /** Project name */ - name: z.string(), - }), - /** URL to view the error in the dashboard */ - dashboardUrl: z.string(), -}); - -export type AlertWebhookErrorGroupObject = z.infer; - -/** Common properties for all webhooks */ -const commonProperties = { - /** Webhook ID */ - id: z.string(), - /** When the webhook was created */ - created: z.coerce.date(), - /** Version of the webhook */ - webhookVersion: z.string(), -}; - -/** Represents all possible webhook types */ -export const Webhook = z.discriminatedUnion("type", [ - /** Run failed alert webhook */ - z.object({ - ...commonProperties, - type: z.literal("alert.run.failed"), - object: AlertWebhookRunFailedObject, - }), - /** Deployment success alert webhook */ - z.object({ - ...commonProperties, - type: z.literal("alert.deployment.success"), - object: AlertWebhookDeploymentSuccessObject, - }), - /** Deployment failed alert webhook */ - z.object({ - ...commonProperties, - type: z.literal("alert.deployment.failed"), - object: AlertWebhookDeploymentFailedObject, - }), - /** Error group alert webhook */ - z.object({ - ...commonProperties, - type: z.literal("alert.error"), - object: AlertWebhookErrorGroupObject, - }), -]); - -export type Webhook = z.infer; -export type RunFailedWebhook = Extract; -export type DeploymentSuccessWebhook = Extract; -export type DeploymentFailedWebhook = Extract; -export type ErrorWebhook = Extract;