Skip to content

feat: harden webhook zod validation for reliability#3354

Closed
alihani714 wants to merge 1 commit intotriggerdotdev:mainfrom
alihani714:main
Closed

feat: harden webhook zod validation for reliability#3354
alihani714 wants to merge 1 commit intotriggerdotdev:mainfrom
alihani714:main

Conversation

@alihani714
Copy link
Copy Markdown

This PR implements stricter Zod validation for webhook schemas to improve reliability and prevent malformed data injection. Specifically, it adds ID prefix validation (run_, task_, etc.), URL sanitization, and data consistency checks for Git metadata.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 10, 2026

⚠️ No Changeset found

Latest commit: 1875295

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

Hi @alihani714, thanks for your interest in contributing!

This project requires that pull request authors are vouched, and you are not in the list of vouched users.

This PR will be closed automatically. See https://github.com/triggerdotdev/trigger.dev/blob/main/CONTRIBUTING.md for more details.

@github-actions github-actions bot closed this Apr 10, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bb09f05a-9764-480e-830e-4516d64e2e59

📥 Commits

Reviewing files that changed from the base of the PR and between bd41bb2 and 1875295.

📒 Files selected for processing (1)
  • packages/core/src/v3/schemas/webhooks.ts

Walkthrough

The change consolidates webhook schema definitions in webhooks.ts by removing deployment success/failure schemas, error-group schemas, and the webhook discriminated union type entirely. The AlertWebhookRunFailedObject schema is now explicitly exported and includes stricter validation: ID fields require specific prefixes (run_, task_, env_, org_, proj\_), task.filePath must be non-empty, run.number must be a positive integer, run.tags is limited to 20 entries of 50 characters each, and run.dashboardUrl must be a valid URL. The optional branchName field from the environment object is removed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 5 potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Massive removal of exported schemas and types breaks multiple consumers

This PR removes the following exports that are actively used across the codebase: Webhook (discriminated union), AlertWebhookDeploymentSuccessObject, AlertWebhookDeploymentFailedObject, AlertWebhookErrorGroupObject, DeployError, RunFailedWebhook, DeploymentSuccessWebhook, DeploymentFailedWebhook, ErrorWebhook, and the AlertWebhookRunFailedObject type alias. These are imported and used in:

  • packages/trigger-sdk/src/v3/webhooks.ts:1 — imports Webhook and calls Webhook.parse() at line 101
  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts:11-14 — imports DeploymentFailedWebhook, DeploymentSuccessWebhook, RunFailedWebhook as types
  • apps/webapp/app/v3/services/alerts/errorGroupWebhook.server.ts:2 — imports ErrorWebhook type
  • apps/webapp/test/errorGroupWebhook.test.ts:2 and apps/webapp/test/webhookErrorAlerts.test.ts:2 — import Webhook for test validation

While TypeScript CI should catch the compilation failures, the semantic impact is worth noting: the entire SDK webhook verification flow (webhooks.constructEvent()) is broken since it depends on Webhook.parse(). This is a breaking change for the published @trigger.dev/sdk package, and per packages/core/CLAUDE.md, changes to @trigger.dev/core affect both the customer-facing SDK and the server-side webapp.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

/** File path where the task is defined */
filePath: z.string(),
/** Name of the exported task function */
id: idWithPrefix(ID_PATTERNS.TASK),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 task.id regex pattern rejects all valid task identifiers

The TASK pattern /^task_[a-zA-Z0-9]+$/ requires a task_ prefix, but the actual value populated in webhook payloads is alert.taskRun.taskIdentifier (deliverAlert.server.ts:397), which is a user-defined slug like "process-payment", "email-sequence", etc. These identifiers contain hyphens and have no task_ prefix, so any call to AlertWebhookRunFailedObject.parse() or .safeParse() on a real webhook payload will fail validation.

Suggested change
id: idWithPrefix(ID_PATTERNS.TASK),
id: z.string().min(1),
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +5 to +11
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]+$/,
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 environment.id, organization.id, and project.id regex patterns reject all valid database CUIDs

The ID patterns ^env_[a-zA-Z0-9]+$, ^org_[a-zA-Z0-9]+$, and ^proj_[a-zA-Z0-9]+$ require specific prefixes, but the actual values used in webhook payloads are Prisma CUIDs (e.g., clxxxxxxxxxxxxxxxxxx) with no prefix. In deliverAlert.server.ts:428, environment.id is set to alert.environment.id (a raw CUID). Similarly, organization.id is alert.project.organizationId (line 434) and project.id is alert.project.id (line 439). All Prisma model primary keys use @default(cuid()) per the schema. Any validation of a real server-generated webhook payload against this schema will fail for all three of these fields.

Suggested change
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 ID_PATTERNS = {
RUN: /^run_[a-zA-Z0-9]+$/,
};
const idWithPrefix = (pattern: RegExp) => z.string().regex(pattern, "Invalid ID format");
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 39 to 43
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(),
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Removed branchName from environment schema breaks webhook API contract

The branchName optional field was removed from the environment object in the schema, but the server still sends it in webhook payloads at deliverAlert.server.ts:431 (branchName: alert.environment.branchName ?? undefined). Zod's default behavior strips unknown keys, so consumers who validate with .parse() will silently lose the branchName field. This is a breaking change to the webhook API contract — consumers who relied on branchName (e.g., to identify preview environment branches) will no longer receive it after validation.

Suggested change
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(),
}),
environment: z.object({
id: idWithPrefix(ID_PATTERNS.ENV),
type: RuntimeEnvironmentTypeSchema,
slug: z.string(),
branchName: z.string().optional(),
}),
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

/** Associated tags */
tags: z.array(z.string()),
/** Error information */
tags: z.array(z.string().max(50)).max(20),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Tags validation constraints may reject existing valid payloads

The new schema adds .max(50) on individual tag strings and .max(20) on the tags array (tags: z.array(z.string().max(50)).max(20)), whereas the old schema had no such constraints (tags: z.array(z.string())). If any existing webhook consumers or server paths produce payloads with more than 20 tags or tags longer than 50 characters, validation would fail. This should be verified against the actual tag limits enforced elsewhere in the system (e.g., at task trigger time) to ensure consistency.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant