fix(webhooks): harden audited provider triggers#3997
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Improves correctness and deduplication by adding/standardizing Adds Salesforce webhook support and trigger wiring via a new Developer tooling/docs: introduces Reviewed by Cursor Bugbot for commit 41b0348. Configure here. |
Greptile SummaryThis PR hardens webhook provider integrations for Salesforce, Zoom, Resend, Linear, Vercel, Greenhouse, Gong, and Notion. It adds missing lifecycle behaviour — server-side event matching, HMAC/JWT signature verification, retry-safe idempotency, subscription create/delete flows, and standardised output schemas — along with targeted tests and a trigger-alignment audit script. Key improvements:
Findings:
Confidence Score: 4/5PR is safe to merge after addressing the fail-open security inconsistency in createHmacVerifier for Greenhouse and Notion webhooks. Score is 4 because the P1 finding (createHmacVerifier silently allowing all requests when no secret is configured) is a real security inconsistency that should be resolved before merging. The existing URL unguessability provides some defence-in-depth, but it is not a substitute for authentication. All other findings are non-blocking P2 style issues. The core webhook hardening logic is well-implemented: timing-safe HMAC comparisons, atomic idempotency claims, correct event filtering, and full subscription lifecycles. apps/sim/lib/webhooks/providers/utils.ts (createHmacVerifier fail-open for Greenhouse and Notion), apps/sim/lib/core/idempotency/service.ts (any types), apps/sim/lib/webhooks/providers/vercel.test.ts (re-mocks global logger), apps/sim/lib/webhooks/providers/salesforce.test.ts (missing @vitest-environment node) Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant Route as /api/webhooks/trigger/[path]
participant Processor
participant Provider as Provider Handler
participant Idempotency
participant Queue
Client->>Route: POST body
Route->>Processor: handleProviderChallenges({})
Note over Processor: Early CRC/challenge (Zoom etc.)
Route->>Processor: parseWebhookBody()
Route->>Processor: handleProviderChallenges(body)
Note over Processor: Challenge with parsed body
Route->>Processor: findAllWebhooksForPath()
loop each webhook (credential fan-out)
Route->>Provider: verifyAuth()
Note over Provider: HMAC / RS256 JWT / Bearer check
Provider-->>Route: null (pass) or 401
Route->>Provider: handleReachabilityTest()
Note over Provider: Notion verification_token etc.
Route->>Processor: checkWebhookPreprocessing()
Route->>Provider: matchEvent()
Note over Provider: Event type / resource filter
Route->>Idempotency: extractIdempotencyId(body)
Idempotency->>Idempotency: atomicallyClaim(Redis NX / PG conflict)
Idempotency-->>Route: claimed or duplicate
Route->>Queue: queueWebhookExecution()
end
Route-->>Client: 200 Webhook processed
|
Align the Greenhouse webhook matcher with provider conventions and clarify the Notion webhook secret setup text after the audit review. Made-with: Cursor
Add salesforce WebhookProviderHandler with required shared secret auth, matchEvent filtering, formatInput aligned to trigger outputs, and idempotency keys. Require webhook secret and document JSON-only Flow setup; enforce objectType when configured. Zoom: pass raw body into URL validation signature check, try all active webhooks on a path for secret match, add extractIdempotencyId, tighten event matching for specialized triggers. Wire Zoom triggers into the Zoom block. Extend handleChallenge with optional rawBody. Register Salesforce pending verification probes for pre-save URL checks.
…outputs) - Dedupe Resend deliveries via svix-id and Linear via Linear-Delivery in idempotency keys - Require Resend signing secret; validate createSubscription id and signing_secret - Single source for Resend event maps in triggers/utils; fail closed on unknown trigger IDs - Add raw event data to Resend trigger outputs and formatInput - Linear: remove body-based idempotency key; timestamp skew after HMAC verify; format url and actorType - Tighten isLinearEventMatch for unknown triggers; clarify generic webhook copy; fix header examples - Add focused tests for idempotency headers and Linear matchEvent
Require Vercel signing secret and validate x-vercel-signature; add matchEvent with dynamic import, delivery idempotency, strict createSubscription trigger IDs, and formatInput aligned to string IDs. Greenhouse: dynamic import in matchEvent, strict unknown trigger IDs, Greenhouse-Event-ID idempotency header, body fallback keys, clearer optional secret copy. Update generic trigger wording and add tests.
- Optional RS256 verification when Gong JWT public key is configured (webhook_url + body_sha256 per Gong docs); URL secrecy when unset. - Document that Gong rules filter calls; payload has no event type; add eventType + callId outputs for discoverability. - Refactor Gong triggers to buildTriggerSubBlocks + shared JWT field; setup copy matches security model. - Add check-trigger-alignment.ts (Gong bundled; extend PROVIDER_CHECKS for others) and update add-trigger guidance paths. Made-with: Cursor
Handle Notion verification requests safely, expose the documented webhook fields in the trigger contract, and update setup guidance so runtime data and user-facing configuration stay aligned. Made-with: Cursor
5af8ade to
729667a
Compare
Close the remaining pre-merge caveats by tightening Salesforce, Zoom, and Linear behavior, and follow through on the deferred provider and tooling cleanup for Vercel, Greenhouse, Gong, and Notion. Made-with: Cursor
Move provider subscription helpers alongside the subscription lifecycle module and add targeted TSDoc so the file placement matches the responsibility boundaries in the webhook architecture. Made-with: Cursor
Use the same env-aware secret resolution path for Zoom endpoint validation as regular delivery verification so URL validation works correctly when the secret token is stored via env references. Made-with: Cursor
Follow-up Hardening ChecklistThis PR looks reasonable to merge. The items below are follow-up hardening/cleanup work, not current blockers.
|
|
@greptile |
|
@cursor review |
Remove dead code in the Salesforce provider and move shared object-type extraction into a single helper so trigger matching and input shaping stay in sync. Made-with: Cursor
|
@greptile |
|
@cursor review |
Loosen Linear's replay window to better tolerate delayed retries and make Notion event mismatches return false consistently with the rest of the hardened providers. Made-with: Cursor
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 41b0348. Configure here.
Move Zoom provider coverage into its own test file and strip undeclared Notion type fields from normalized output objects so the runtime shape better matches the trigger contract. Made-with: Cursor
Document and pass through Vercel links, regions, deployment.meta, and domain.delegated; add top-level Greenhouse applicationId, candidateId, and jobId aligned with webhook common attributes. Extend alignment checker for greenhouse, update provider docs, and add formatInput tests. Made-with: Cursor
…docs - Resend: expose broadcast_id, template_id, tags, and data_created_at from payload data (per Resend webhook docs); keep alignment with formatInput. - Add resend entry to check-trigger-alignment and unit test for formatInput. - Notion: tighten output descriptions for authors, entity types, parent types, attempt_number, and accessible_by per Notion webhooks event reference. Made-with: Cursor
- Zoom: add formatInput passthrough, fix nested TriggerOutput shape (drop invalid `properties` wrappers), document host_email, join_url, agenda, status, meeting_type on recordings, participant duration, and alignment checker entry. - Gong: flatten topics/highlights from callData.content in formatInput, extend metaData and trigger outputs per API docs, tests and alignment keys updated. - Docs: add English webhook trigger sections for Zoom and Gong tools pages.
Salesforce: expose simEventType alongside eventType; pass OwnerId and SystemModstamp on record lifecycle inputs; add AccountId/OwnerId for Opportunity and AccountId/ContactId/OwnerId for Case. Align trigger output docs with Flow JSON payloads and formatInput. Linear: document actor email and profile url per official webhook payload; add Comment data.edited from Linear's sample payload. Tests: extend Salesforce formatInput coverage for new fields.
Extend the trigger alignment checker to cover additional webhook providers so output contracts are verified across more of the recently added trigger surface. Made-with: Cursor
* fix(triggers): apply webhook audit follow-ups Align the Greenhouse webhook matcher with provider conventions and clarify the Notion webhook secret setup text after the audit review. Made-with: Cursor * fix(webhooks): Salesforce provider handler, Zoom CRC and block wiring Add salesforce WebhookProviderHandler with required shared secret auth, matchEvent filtering, formatInput aligned to trigger outputs, and idempotency keys. Require webhook secret and document JSON-only Flow setup; enforce objectType when configured. Zoom: pass raw body into URL validation signature check, try all active webhooks on a path for secret match, add extractIdempotencyId, tighten event matching for specialized triggers. Wire Zoom triggers into the Zoom block. Extend handleChallenge with optional rawBody. Register Salesforce pending verification probes for pre-save URL checks. * fix(webhooks): harden Resend and Linear triggers (idempotency, auth, outputs) - Dedupe Resend deliveries via svix-id and Linear via Linear-Delivery in idempotency keys - Require Resend signing secret; validate createSubscription id and signing_secret - Single source for Resend event maps in triggers/utils; fail closed on unknown trigger IDs - Add raw event data to Resend trigger outputs and formatInput - Linear: remove body-based idempotency key; timestamp skew after HMAC verify; format url and actorType - Tighten isLinearEventMatch for unknown triggers; clarify generic webhook copy; fix header examples - Add focused tests for idempotency headers and Linear matchEvent * fix(webhooks): harden Vercel and Greenhouse trigger handlers Require Vercel signing secret and validate x-vercel-signature; add matchEvent with dynamic import, delivery idempotency, strict createSubscription trigger IDs, and formatInput aligned to string IDs. Greenhouse: dynamic import in matchEvent, strict unknown trigger IDs, Greenhouse-Event-ID idempotency header, body fallback keys, clearer optional secret copy. Update generic trigger wording and add tests. * fix(gong): JWT verification, trigger UX, alignment script - Optional RS256 verification when Gong JWT public key is configured (webhook_url + body_sha256 per Gong docs); URL secrecy when unset. - Document that Gong rules filter calls; payload has no event type; add eventType + callId outputs for discoverability. - Refactor Gong triggers to buildTriggerSubBlocks + shared JWT field; setup copy matches security model. - Add check-trigger-alignment.ts (Gong bundled; extend PROVIDER_CHECKS for others) and update add-trigger guidance paths. Made-with: Cursor * fix(notion): align webhook lifecycle and outputs Handle Notion verification requests safely, expose the documented webhook fields in the trigger contract, and update setup guidance so runtime data and user-facing configuration stay aligned. Made-with: Cursor * fix(webhooks): tighten remaining provider hardening Close the remaining pre-merge caveats by tightening Salesforce, Zoom, and Linear behavior, and follow through on the deferred provider and tooling cleanup for Vercel, Greenhouse, Gong, and Notion. Made-with: Cursor * refactor(webhooks): move subscription helpers out of providers Move provider subscription helpers alongside the subscription lifecycle module and add targeted TSDoc so the file placement matches the responsibility boundaries in the webhook architecture. Made-with: Cursor * fix(zoom): resolve env-backed secrets during validation Use the same env-aware secret resolution path for Zoom endpoint validation as regular delivery verification so URL validation works correctly when the secret token is stored via env references. Made-with: Cursor * fix build * consolidate tests * refactor(salesforce): share payload object type parsing Remove dead code in the Salesforce provider and move shared object-type extraction into a single helper so trigger matching and input shaping stay in sync. Made-with: Cursor * fix(webhooks): address remaining review follow-ups Loosen Linear's replay window to better tolerate delayed retries and make Notion event mismatches return false consistently with the rest of the hardened providers. Made-with: Cursor * test(webhooks): separate Zoom coverage and clean Notion output shape Move Zoom provider coverage into its own test file and strip undeclared Notion type fields from normalized output objects so the runtime shape better matches the trigger contract. Made-with: Cursor * feat(triggers): enrich Vercel and Greenhouse webhook output shapes Document and pass through Vercel links, regions, deployment.meta, and domain.delegated; add top-level Greenhouse applicationId, candidateId, and jobId aligned with webhook common attributes. Extend alignment checker for greenhouse, update provider docs, and add formatInput tests. Made-with: Cursor * feat(webhooks): enrich Resend trigger outputs; clarify Notion output docs - Resend: expose broadcast_id, template_id, tags, and data_created_at from payload data (per Resend webhook docs); keep alignment with formatInput. - Add resend entry to check-trigger-alignment and unit test for formatInput. - Notion: tighten output descriptions for authors, entity types, parent types, attempt_number, and accessible_by per Notion webhooks event reference. Made-with: Cursor * feat(webhooks): enrich Zoom and Gong trigger output schemas - Zoom: add formatInput passthrough, fix nested TriggerOutput shape (drop invalid `properties` wrappers), document host_email, join_url, agenda, status, meeting_type on recordings, participant duration, and alignment checker entry. - Gong: flatten topics/highlights from callData.content in formatInput, extend metaData and trigger outputs per API docs, tests and alignment keys updated. - Docs: add English webhook trigger sections for Zoom and Gong tools pages. * feat(triggers): enrich Salesforce and Linear webhook output schemas Salesforce: expose simEventType alongside eventType; pass OwnerId and SystemModstamp on record lifecycle inputs; add AccountId/OwnerId for Opportunity and AccountId/ContactId/OwnerId for Case. Align trigger output docs with Flow JSON payloads and formatInput. Linear: document actor email and profile url per official webhook payload; add Comment data.edited from Linear's sample payload. Tests: extend Salesforce formatInput coverage for new fields. * remove from mdx * chore(webhooks): expand trigger alignment coverage Extend the trigger alignment checker to cover additional webhook providers so output contracts are verified across more of the recently added trigger surface. Made-with: Cursor * updated skills * updated file naming semantics * rename file

Summary
Test plan