Skip to content

Alert Webhook improvements #1703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 13, 2025
Merged

Alert Webhook improvements #1703

merged 9 commits into from
Feb 13, 2025

Conversation

matt-aitken
Copy link
Member

@matt-aitken matt-aitken commented Feb 12, 2025

Improved the alert webhooks data and added an SDK function to verify and parse them (similar to Stripe SDK).

const event = await webhooks.constructEvent(request, process.env.ALERT_WEBHOOK_SECRET!);

If the signature you provide matches the one from the dashboard when you create the webhook, you will get a nicely typed object back for these three types:

  • Run fails
  • Deployment fails
  • Deployment succeeds

Summary by CodeRabbit

  • New Features

    • Alerts now support versioned webhook payloads, delivering improved structure and richer deployment and failure notifications.
    • A new testing endpoint enables easy simulation and validation of webhook events.
    • Enhanced webhook integration in our SDK offers improved security and streamlined error handling.
    • New property channelType added to alert creation processes for better data structure.
  • Documentation

    • Updated guides provide clear, step-by-step instructions for setting up alerts and managing webhook events.

Copy link
Contributor

coderabbitai bot commented Feb 12, 2025

Walkthrough

The pull request introduces a series of changes across multiple modules to enhance the webhook and alert functionality. Updates include adding a version property to webhook schemas (with distinct defaults for different contexts), a new route for testing webhooks, modifications in the alert delivery service to handle versioned payloads, and adjustments to the export of utility functions. Additionally, new webhook schemas and a dedicated webhook module with signature verification and error handling are added, alongside enhanced documentation on alert setup and usage.

Changes

File(s) Change Summary
apps/webapp/app/models/projectAlert.server.ts
apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
Added a version property to webhook properties; default set to "v1" in the ProjectAlert schema and "v2" in alert channel creation.
apps/webapp/app/routes/internal.webhooks.tester.ts Introduced a new route for testing webhooks with a POST handler that clones requests, verifies webhook events using a secret, and handles errors.
apps/webapp/app/v3/services/alerts/deliverAlert.server.ts Updated the DeliverAlertService to support versioned webhook payloads; modified the #deliverWebhook method to be generic and include version-based switch logic.
apps/webapp/app/v3/services/completeAttempt.server.ts Changed the isOOMError function from a local to an exported function for wider accessibility.
docs/troubleshooting-alerts.mdx Enhanced documentation by adding sections on "How to setup alerts" and "Alert webhooks", including a code snippet illustrating webhook payload parsing and error handling.
packages/core/src/v3/schemas/index.ts
packages/core/src/v3/schemas/webhooks.ts
Exported a new webhooks module; introduced multiple webhook-related schemas and types for validating alert payloads.
packages/trigger-sdk/src/v3/index.ts
packages/trigger-sdk/src/v3/webhooks.ts
Added a new webhook module with functionality for error handling, signature verification, and event construction, including the WebhookError class and related types.
packages/trigger-sdk/package.json Added a new dependency on the uncrypto package.
packages/trigger-sdk/src/imports/uncrypto-cjs.cts
packages/trigger-sdk/src/imports/uncrypto.ts
Imported and exported the subtle module from the uncrypto package.

Suggested reviewers

  • ericallam

Poem

Hopping through the lines of code so bright,
I nibble on changes from morning to night,
Versions added with a pitter-patter of feet,
Webhooks dancing gracefully in the heat,
Errors chased away under the moon's gleam,
A rabbit's cheer for every refined dream! 🐇

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

apps/webapp/app/v3/services/alerts/performTaskRunAlerts.server.ts

Oops! Something went wrong! :(

ESLint: 8.45.0

ESLint couldn't find the config "custom" to extend from. Please check that the name of the config is correct.

The config "custom" was referenced from the config file in "/.eslintrc.js".

If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.

packages/trigger-sdk/src/v3/index.ts

Oops! Something went wrong! :(

ESLint: 8.45.0

ESLint couldn't find the config "custom" to extend from. Please check that the name of the config is correct.

The config "custom" was referenced from the config file in "/.eslintrc.js".

If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.

apps/webapp/app/v3/services/alerts/deliverAlert.server.ts

Oops! Something went wrong! :(

ESLint: 8.45.0

ESLint couldn't find the config "custom" to extend from. Please check that the name of the config is correct.

The config "custom" was referenced from the config file in "/.eslintrc.js".

If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.

  • 3 others
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (9)
packages/trigger-sdk/src/v3/webhooks.ts (3)

7-12: Use more descriptive error messages for better debugging support.

Currently, the WebhookError class only includes a generic message. You could provide an optional rendering of relevant context (e.g., missing header name, request info) to enhance diagnostic clarity without exposing sensitive information.


70-80: Check for strict content-type handling.

When deriving the signature from a Request, you may want to verify the Content-Type header (e.g., application/json) to avoid confusion if the payload is unexpectedly encoded in another format.


85-87: Extend or revise the array-header error message.

The message “Signature header cannot be an array” is valid, but consider clarifying which header is invalid and possibly log the raw header for debugging, ensuring sensitive data is masked.

packages/core/src/v3/schemas/webhooks.ts (1)

97-117: Consider DRY approach for environment, organization, and project shapes.

Although you’ve extracted deploymentCommonProperties, you might also unify or alias these environment/org/project schemas for both run and deployment objects, reducing repetition and potential drift.

apps/webapp/app/v3/services/alerts/deliverAlert.server.ts (2)

316-412: Reduce repetitive code for v1/v2 payload construction.

In the switch statement for run alerts, the logic for “v1” and “v2” shares many similarities. Extracting the shared fields into a helper can reduce repetition.

+ // Example: A small helper function returning a base payload object:
+ function buildBaseRunPayload(alert: FoundAlert) { ... }

switch (webhookProperties.data.version) {
  case "v1":
-   const payload = { /* large object with many fields */ };
+   const payload = { 
+     ...buildBaseRunPayload(alert),
+     // v1-specific fields
+   };
    ...
  case "v2":
-   const payload: RunFailedWebhook = { /* large object with many fields */ };
+   const payload: RunFailedWebhook = {
+     ...buildBaseRunPayload(alert),
+     // v2-specific fields
+   };
    ...
}

909-948: Validate retry requirements for non-200 responses.

Currently, if the request fails, the code throws an error and logs it. Confirm whether you need a retry mechanism for transient network failures (e.g., 502 status or timeouts).

apps/webapp/app/models/projectAlert.server.ts (1)

7-7: LGTM! Consider adding documentation.

The version property is correctly defined as optional with a default value of "v1". Consider adding a comment to document the purpose of this property and its supported values.

+  /** Version of the webhook payload format. Defaults to "v1". */
   version: z.string().optional().default("v1"),
apps/webapp/app/routes/internal.webhooks.tester.ts (1)

41-44: Improve error handling.

Consider logging the error stack trace for better debugging.

     if (err instanceof Error) {
-      logger.error("[Webhook Internal Test] Error processing webhook:", { message: err.message });
+      logger.error("[Webhook Internal Test] Error processing webhook:", { 
+        message: err.message,
+        stack: err.stack 
+      });
       return json({ error: err.message }, { status: 400 });
     }
docs/troubleshooting-alerts.mdx (1)

6-9: Improve grammar and readability.

Consider the following improvements:

  1. Remove redundancy in the list of events:
-We support receiving alerts for the following events:
-- Run fails
-- Deployment fails
-- Deployment succeeds
+We support alerts for:
+- Run failures
+- Deployment failures
+- Deployment successes
  1. Add a comma for better readability:
-For the alert webhooks you can use the SDK to parse them.
+For the alert webhooks, you can use the SDK to parse them.

Also applies to: 41-41

🧰 Tools
🪛 LanguageTool

[grammar] ~7-~7: This phrase is duplicated. You should probably use “fails Deployment” only once.
Context: ... alerts for the following events: - Run fails - Deployment fails - Deployment succeeds ## How to setup alerts <Step...

(PHRASE_REPETITION)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a38f713 and b09d875.

📒 Files selected for processing (10)
  • apps/webapp/app/models/projectAlert.server.ts (1 hunks)
  • apps/webapp/app/routes/internal.webhooks.tester.ts (1 hunks)
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts (1 hunks)
  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts (8 hunks)
  • apps/webapp/app/v3/services/completeAttempt.server.ts (1 hunks)
  • docs/troubleshooting-alerts.mdx (2 hunks)
  • packages/core/src/v3/schemas/index.ts (1 hunks)
  • packages/core/src/v3/schemas/webhooks.ts (1 hunks)
  • packages/trigger-sdk/src/v3/index.ts (1 hunks)
  • packages/trigger-sdk/src/v3/webhooks.ts (1 hunks)
🧰 Additional context used
🪛 LanguageTool
docs/troubleshooting-alerts.mdx

[grammar] ~7-~7: This phrase is duplicated. You should probably use “fails Deployment” only once.
Context: ... alerts for the following events: - Run fails - Deployment fails - Deployment succeeds ## How to setup alerts <Step...

(PHRASE_REPETITION)


[uncategorized] ~41-~41: Possible missing comma found.
Context: ...eps> ## Alert webhooks For the alert webhooks you can use the SDK to parse them. Here...

(AI_HYDRA_LEO_MISSING_COMMA)

🔇 Additional comments (10)
packages/trigger-sdk/src/v3/webhooks.ts (2)

49-53: Add reminder about test coverage.

The constructEvent method is central to verifying and parsing incoming requests. Ensure it’s covered by unit or integration tests to validate real-world edge cases (e.g., empty payload, array header, incorrect signature).


153-155: Ensure no collisions or length mismatches in signature comparison.

The timingSafeEqual function correctly protects against timing attacks for equal-length signatures. However, confirm that the upstream logic never truncates or modifies the signature string in a way that might bypass length checks.

packages/core/src/v3/schemas/webhooks.ts (2)

1-5: Schema coverage looks great.

The usage of Zod for typed validation is well-structured. The reference to RuntimeEnvironmentTypeSchema, RunStatus, and TaskRunError ensures consistency across the codebase.


181-200: Expand tested union types with direct references.

The discriminated union approach is solid. To improve test coverage, consider writing tests that feed each possible type payload (run failed, deployment success, deployment failed) into Webhook.parse to ensure end-to-end reliability.

apps/webapp/app/v3/services/alerts/deliverAlert.server.ts (1)

356-561: Double-check the webhookVersion property for “v2” payloads.

When constructing the “v2” payload, you're setting webhookVersion: "v1". This may be confusing or unintentional if you plan to track version differences downstream.

packages/core/src/v3/schemas/index.ts (1)

13-13: LGTM!

The new export statement for the webhooks module aligns with the PR objectives and follows the established pattern of module exports.

packages/trigger-sdk/src/v3/index.ts (1)

15-15: LGTM!

The new export statement for the webhooks module aligns with the PR objectives and follows the established pattern of module exports.

docs/troubleshooting-alerts.mdx (1)

41-96: LGTM! The webhook example is well-implemented.

The webhook implementation includes:

  • Proper request method validation
  • Comprehensive error handling
  • Type-safe event processing
  • Clear logging
🧰 Tools
🪛 LanguageTool

[uncategorized] ~41-~41: Possible missing comma found.
Context: ...eps> ## Alert webhooks For the alert webhooks you can use the SDK to parse them. Here...

(AI_HYDRA_LEO_MISSING_COMMA)

apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts (1)

103-103: Verify the impact of hardcoding webhook version to "v2".

The hardcoded version "v2" might affect existing webhook integrations. Please ensure that:

  1. The webhook handler maintains backward compatibility with "v1" payloads.
  2. Existing webhook integrations are notified about the version change.

Run the following script to check for existing webhook channels:

❌ Verification inconclusive

🏁 Script executed:

#!/bin/bash
# Description: Check for existing webhook channels that might be affected by the version change.

# Test: Search for webhook channel creation. Expect: Existing webhook channels.
ast-grep --pattern $'type: "WEBHOOK"'

Length of output: 38


Action Required: Manual Verification for Webhook Version Impact

The automated search using the provided script did not yield any results for existing webhook channels. This lack of output makes it difficult to infer whether existing integrations are affected or if they are implemented using a different pattern.

  • Manual Check Needed: Please review the webhook handler code to ensure that it supports backward compatibility with "v1" payloads.
  • Notification Validation: Confirm that any consumers of webhook integrations have been properly notified regarding the switch to "v2".
  • Further Investigation: If the integration setup is more complex or uses a different mechanism to register webhook channels, a manual code inspection or a targeted search for alternative patterns may be necessary.
apps/webapp/app/v3/services/completeAttempt.server.ts (1)

741-778: LGTM! Well-documented OOM error detection.

The function is now exported, making it reusable across modules. The implementation thoroughly handles various OOM error cases with clear documentation.

🧰 Tools
🪛 Biome (1.9.4)

[error] 766-766: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Copy link

changeset-bot bot commented Feb 13, 2025

🦋 Changeset detected

Latest commit: d033444

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@trigger.dev/sdk Patch
@trigger.dev/build Patch
@trigger.dev/core Patch
@trigger.dev/react-hooks Patch
@trigger.dev/rsc Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
trigger.dev Patch
@internal/redis-worker Patch
@internal/zod-worker Patch
references-nextjs-realtime Patch
@internal/testcontainers Patch

Not sure what this means? Click here to learn what changesets are.

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
apps/webapp/app/v3/services/alerts/deliverAlert.server.ts (2)

206-206: Consider adding a deprecation notice period.

The task run attempt alerts are being deprecated immediately. Consider adding a deprecation notice period to allow users to migrate to the new alert types.

Also applies to: 309-309, 665-665


316-412: Verify webhook version handling.

The webhook version handling looks good but let's verify a few things:

  1. The webhookVersion field in v2 payloads is hardcoded to "v1". Is this intentional?
  2. The error message for unknown versions could be more descriptive.

Apply this diff to improve error handling:

-              throw new Error(`Unknown webhook version: ${webhookProperties.data.version}`);
+              throw new Error(
+                `Unsupported webhook version: ${webhookProperties.data.version}. ` +
+                `Supported versions are: v1, v2`
+              );

Also applies to: 435-508, 519-604

.changeset/slow-olives-fix.md (1)

5-16: Enhance the documentation with error handling examples.

The documentation would benefit from:

  1. Examples of error handling when the signature verification fails
  2. Description of the webhook payload structure for each event type
  3. Instructions for securely storing the webhook secret
packages/trigger-sdk/src/v3/webhooks.ts (3)

63-109: Add additional error handling for edge cases.

While the error handling is good, consider handling these additional edge cases:

  1. Empty payload
  2. Malformed JSON before parsing
  3. Maximum payload size limit

Here's a suggested implementation:

 async function constructEvent(
   request: ConstructEventOptions | Request,
   secret: string
 ): Promise<Webhook> {
   let payload: string;
   let signature: string;
+  const MAX_PAYLOAD_SIZE = 5 * 1024 * 1024; // 5MB limit
 
   if (request instanceof Request) {
     if (!secret) {
       throw new WebhookError("Secret is required when passing a Request object");
     }
 
+    const contentLength = request.headers.get("content-length");
+    if (contentLength && parseInt(contentLength) > MAX_PAYLOAD_SIZE) {
+      throw new WebhookError("Payload size exceeds limit");
+    }
+
     const signatureHeader = request.headers.get(SIGNATURE_HEADER_NAME);
     if (!signatureHeader) {
       throw new WebhookError("No signature header found");
     }
     signature = signatureHeader;
 
     payload = await request.text();
   } else {
     payload = request.payload.toString();
 
     if (Array.isArray(request.header)) {
       throw new WebhookError("Signature header cannot be an array");
     }
     signature = request.header.toString();
   }
 
+  if (!payload.trim()) {
+    throw new WebhookError("Empty payload");
+  }
+
   // Verify the signature
   const isValid = await verifySignature(payload, signature, secret);
 
   if (!isValid) {
     throw new WebhookError("Invalid signature");
   }
 
   // Parse and validate the payload
   try {
+    // Check for valid JSON structure before parsing
+    if (!payload.startsWith("{") && !payload.startsWith("[")) {
+      throw new WebhookError("Malformed JSON payload");
+    }
     const jsonPayload = JSON.parse(payload);
     const parsedPayload = Webhook.parse(jsonPayload);
     return parsedPayload;
   } catch (error) {
     if (error instanceof Error) {
       throw new WebhookError(`Webhook parsing failed: ${error.message}`);
     }
     throw new WebhookError("Webhook parsing failed");
   }
 }

111-158: Add input validation for signature and secret.

Consider adding validation for:

  1. Minimum secret length
  2. Expected signature format (hex)

Here's a suggested implementation:

 async function verifySignature(
   payload: string,
   signature: string,
   secret: string
 ): Promise<boolean> {
   try {
     if (!secret) {
       throw new WebhookError("Secret is required for signature verification");
     }
 
+    // Validate minimum secret length (32 bytes recommended for HMAC-SHA256)
+    if (secret.length < 32) {
+      throw new WebhookError("Secret must be at least 32 characters long");
+    }
+
+    // Validate signature format (hex)
+    if (!/^[a-f0-9]+$/i.test(signature)) {
+      throw new WebhookError("Invalid signature format");
+    }
+
     // Convert the payload and secret to buffers
     const hashPayload = Buffer.from(payload, "utf-8");
     const hmacSecret = Buffer.from(secret, "utf-8");

160-171: Consider using crypto.timingSafeEqual.

While the current implementation is correct, Node.js's crypto.timingSafeEqual is a battle-tested alternative that's specifically designed for this purpose.

Here's a suggested implementation:

-// Timing-safe comparison to prevent timing attacks
-function timingSafeEqual(a: string, b: string): boolean {
-  if (a.length !== b.length) {
-    return false;
-  }
-
-  let result = 0;
-  for (let i = 0; i < a.length; i++) {
-    result |= a.charCodeAt(i) ^ b.charCodeAt(i);
-  }
-  return result === 0;
-}
+import { timingSafeEqual as cryptoTimingSafeEqual } from "crypto";
+
+// Timing-safe comparison to prevent timing attacks
+function timingSafeEqual(a: string, b: string): boolean {
+  if (a.length !== b.length) {
+    return false;
+  }
+  return cryptoTimingSafeEqual(Buffer.from(a), Buffer.from(b));
+}

Note: If you need to maintain compatibility with non-Node.js platforms, the current implementation is a good fallback.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b09d875 and d033444.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • .changeset/slow-olives-fix.md (1 hunks)
  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts (10 hunks)
  • apps/webapp/app/v3/services/alerts/performDeploymentAlerts.server.ts (1 hunks)
  • apps/webapp/app/v3/services/alerts/performTaskRunAlerts.server.ts (1 hunks)
  • packages/trigger-sdk/package.json (1 hunks)
  • packages/trigger-sdk/src/imports/uncrypto-cjs.cts (1 hunks)
  • packages/trigger-sdk/src/imports/uncrypto.ts (1 hunks)
  • packages/trigger-sdk/src/v3/index.ts (1 hunks)
  • packages/trigger-sdk/src/v3/webhooks.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • packages/trigger-sdk/src/imports/uncrypto.ts
  • packages/trigger-sdk/src/imports/uncrypto-cjs.cts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/trigger-sdk/src/v3/index.ts
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: units / 🧪 Unit Tests
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
apps/webapp/app/v3/services/alerts/performTaskRunAlerts.server.ts (1)

52-52: LGTM!

The addition of channelType to the alert payload aligns with the webhook improvements, ensuring consistent alert delivery across different channel types.

apps/webapp/app/v3/services/alerts/performDeploymentAlerts.server.ts (1)

52-52: LGTM!

The addition of channelType to the alert payload is consistent with the webhook improvements and matches the changes in performTaskRunAlerts.server.ts.

apps/webapp/app/v3/services/alerts/deliverAlert.server.ts (3)

9-16: LGTM!

The new imports from @trigger.dev/core/v3 support the webhook improvements by providing strongly-typed webhook payloads.


909-948: LGTM! Robust webhook delivery implementation.

The generic #deliverWebhook method provides a type-safe way to deliver webhooks with proper signature verification and error handling. The 5-second timeout is a good practice to prevent hanging connections.


1088-1112: LGTM! Rate limiting for non-webhook channels.

The rate limiting logic is well-implemented:

  • Only applies to non-webhook channels
  • Includes proper error handling
  • Logs rate limit events for monitoring
packages/trigger-sdk/src/v3/webhooks.ts (1)

27-61: Well-documented interface with clear examples!

The Webhooks interface and its implementation are well-structured with:

  • Clear JSDoc documentation
  • Practical usage examples
  • Proper type definitions

@@ -56,6 +56,7 @@
"slug": "^6.0.0",
"terminal-link": "^3.0.0",
"ulid": "^2.3.0",
"uncrypto": "^0.1.3",
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify the stability and security of the uncrypto package.

The package is at version 0.1.3 which suggests it's in early development. Since it's used for security-critical webhook signature verification, we should ensure it's stable and secure.

Additionally, consider pinning the exact version (0.1.3 instead of ^0.1.3) to avoid potential breaking changes, as 0.x versions may not follow semantic versioning strictly.

-    "uncrypto": "^0.1.3",
+    "uncrypto": "0.1.3",

🏁 Script executed:

#!/bin/bash
# Description: Check package details and security advisories for uncrypto

# Check npm for latest versions and download stats
curl -s https://registry.npmjs.org/uncrypto | jq '{latest: .["dist-tags"].latest, versions: .versions, time: .time}'

# Check for security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "uncrypto") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 16219


🌐 Web query:

What is the latest version of uncrypto? Is it actively maintained and secure for production use?

💡 Result:

The latest version of uncrypto is 0.1.3, as shown in its package.json file[5]. This cross-runtime cryptographic library provides a unified API for Web Crypto and Node.js Crypto modules[1][4].

Maintenance Status

  • The GitHub repository shows active maintenance with recent commits (as of June 2023)[7] and clear contribution guidelines[1].
  • Development requirements include modern tooling (Node.js v15+, pnpm)[1].
  • Automated testing via Vitest is implemented[5].

Security Considerations

  1. Underlying Security:

    • Uses platform-native crypto implementations (Web Crypto API/Node.js Crypto)[4][7]
    • Inherits security from battle-tested systems like CommonCrypto on macOS/iOS[4]
  2. Production Readiness:

    • Implements secure key handling by keeping secrets outside JavaScript execution context[4]
    • Aligns with Node.js' stabilized Permissions Model for defense-in-depth security[8]
    • Requires secure contexts (HTTPS/localhost) in browsers[1]
  3. Limitations:

    • Version 0.x indicates pre-1.0 status (caution advised for critical systems)[5]
    • No recent security audits mentioned in available documentation

While not explicitly marked "production-ready," its design leverages secure patterns from modern JavaScript ecosystems[1][8]. For non-critical use cases requiring cross-platform crypto operations, it appears suitable when used with standard security precautions like HTTPS and updated dependencies[4][8].

Citations:


Uncrypto Dependency Verification & Version Pinning

The new dependency uncrypto at version ^0.1.3 has been verified:

  • Latest Version & Maintenance: The npm registry confirms the latest version is 0.1.3, and the repository shows active maintenance with recent activity.
  • Security Status: No vulnerabilities have been reported in the security advisories for this package.
  • Recommendation: As this package (being in its 0.x phase) might introduce breaking changes, it's advisable to pin the version explicitly to "0.1.3" rather than using the caret range. This minimizes any unexpected impacts on our webhook signature verification code.

Actionable Change:

-    "uncrypto": "^0.1.3",
+    "uncrypto": "0.1.3",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"uncrypto": "^0.1.3",
"uncrypto": "0.1.3",

Comment on lines +14 to +15
/** Header name used for webhook signatures */
const SIGNATURE_HEADER_NAME = "x-trigger-signature-hmacsha256";
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify the security header naming convention.

The header name x-trigger-signature-hmacsha256 follows good practices by:

  1. Using the x- prefix for custom headers
  2. Including the hash algorithm in the name

Let's verify if this header name is consistently used across the codebase:


🏁 Script executed:

#!/bin/bash
# Description: Check for consistent usage of the signature header name
rg -i "trigger.*signature" --type ts

Length of output: 372


Inconsistent header naming found in the codebase.

  • The constant SIGNATURE_HEADER_NAME is defined as "x-trigger-signature-hmacsha256" in packages/trigger-sdk/src/v3/webhooks.ts and used as such in apps/webapp/app/v3/services/alerts/deliverAlert.server.ts.
  • However, in apps/webapp/app/services/runs/deliverRunSubscription.server.ts, it is referenced as "X-Trigger-Signature-256", which deviates from the defined convention.
  • Action Required: Update the header name in deliverRunSubscription.server.ts to either import and use SIGNATURE_HEADER_NAME or change it to "x-trigger-signature-hmacsha256" for consistency and adherence to common security practices.

Comment on lines +70 to +81
if (request instanceof Request) {
if (!secret) {
throw new WebhookError("Secret is required when passing a Request object");
}

const signatureHeader = request.headers.get(SIGNATURE_HEADER_NAME);
if (!signatureHeader) {
throw new WebhookError("No signature header found");
}
signature = signatureHeader;

payload = await request.text();
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add security headers validation.

Consider validating additional security headers to prevent replay attacks:

  1. x-trigger-timestamp for request freshness
  2. x-trigger-idempotency-key for idempotency

Here's a suggested implementation:

   if (request instanceof Request) {
     if (!secret) {
       throw new WebhookError("Secret is required when passing a Request object");
     }
 
+    // Validate timestamp to prevent replay attacks
+    const timestamp = request.headers.get("x-trigger-timestamp");
+    if (!timestamp) {
+      throw new WebhookError("No timestamp header found");
+    }
+    const requestTime = parseInt(timestamp);
+    if (isNaN(requestTime)) {
+      throw new WebhookError("Invalid timestamp");
+    }
+    const now = Date.now() / 1000;
+    if (Math.abs(now - requestTime) > 300) { // 5 minutes tolerance
+      throw new WebhookError("Request too old");
+    }
+
+    // Validate idempotency key
+    const idempotencyKey = request.headers.get("x-trigger-idempotency-key");
+    if (!idempotencyKey) {
+      throw new WebhookError("No idempotency key found");
+    }
+
     const signatureHeader = request.headers.get(SIGNATURE_HEADER_NAME);
     if (!signatureHeader) {
       throw new WebhookError("No signature header found");
     }
     signature = signatureHeader;
 
     payload = await request.text();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (request instanceof Request) {
if (!secret) {
throw new WebhookError("Secret is required when passing a Request object");
}
const signatureHeader = request.headers.get(SIGNATURE_HEADER_NAME);
if (!signatureHeader) {
throw new WebhookError("No signature header found");
}
signature = signatureHeader;
payload = await request.text();
if (request instanceof Request) {
if (!secret) {
throw new WebhookError("Secret is required when passing a Request object");
}
// Validate timestamp to prevent replay attacks
const timestamp = request.headers.get("x-trigger-timestamp");
if (!timestamp) {
throw new WebhookError("No timestamp header found");
}
const requestTime = parseInt(timestamp);
if (isNaN(requestTime)) {
throw new WebhookError("Invalid timestamp");
}
const now = Date.now() / 1000;
if (Math.abs(now - requestTime) > 300) { // 5 minutes tolerance
throw new WebhookError("Request too old");
}
// Validate idempotency key
const idempotencyKey = request.headers.get("x-trigger-idempotency-key");
if (!idempotencyKey) {
throw new WebhookError("No idempotency key found");
}
const signatureHeader = request.headers.get(SIGNATURE_HEADER_NAME);
if (!signatureHeader) {
throw new WebhookError("No signature header found");
}
signature = signatureHeader;
payload = await request.text();
}

Comment on lines +1 to +2
import { Webhook } from "@trigger.dev/core/v3";
import { subtle } from "../imports/uncrypto.js";
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify the crypto implementation choice.

Based on the past review discussion about crypto module choices, we should verify that the uncrypto.js implementation provides the necessary compatibility across different platforms (Node.js, Bun, Deno, CF Workers, Vercel Edge).

Let me help verify the implementation:


🏁 Script executed:

#!/bin/bash
# Description: Check the uncrypto.js implementation
# Test: Look for the uncrypto.js file and its implementation
fd -t f "uncrypto.js" | xargs cat

Length of output: 33


I'll run a new shell script to explicitly check for the existence and content of packages/trigger-sdk/src/imports/uncrypto.js to better understand the crypto implementation reference.


🏁 Script executed:

#!/bin/bash
# Check for uncrypto.js at the expected relative location
FILE="packages/trigger-sdk/src/imports/uncrypto.js"
if [ -f "$FILE" ]; then
  echo "File found: $FILE"
  # Display file size and first 50 lines for a quick review
  wc -c "$FILE"
  head -n 50 "$FILE"
else
  echo "File not found: $FILE"
fi

Length of output: 240


It appears that the file referenced by the import—../imports/uncrypto.js—is not present in the repository, which means that the crypto implementation under review isn’t available as expected. Before concluding, could you please confirm if the file was intentionally removed or moved to a different location? If it was meant to be removed or replaced by another module, kindly update the import and corresponding usage accordingly.

@matt-aitken matt-aitken merged commit 440d413 into main Feb 13, 2025
12 checks passed
@matt-aitken matt-aitken deleted the webhook-improvements branch February 13, 2025 13:54
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.

2 participants