Skip to content

Commit 45d159c

Browse files
waleedlatif1emir-karabeg
authored andcommitted
fix(webhooks): harden audited provider triggers (#3997)
* 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
1 parent 0cc077b commit 45d159c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+3601
-1606
lines changed

.agents/skills/add-trigger/SKILL.md

Lines changed: 159 additions & 509 deletions
Large diffs are not rendered by default.
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
---
2+
name: validate-trigger
3+
description: Audit an existing Sim webhook trigger against the service's webhook API docs and repository conventions, then report and fix issues across trigger definitions, provider handler, output alignment, registration, and security. Use when validating or repairing a trigger under `apps/sim/triggers/{service}/` or `apps/sim/lib/webhooks/providers/{service}.ts`.
4+
---
5+
6+
# Validate Trigger
7+
8+
You are an expert auditor for Sim webhook triggers. Your job is to validate that an existing trigger implementation is correct, complete, secure, and aligned across all layers.
9+
10+
## Your Task
11+
12+
1. Read the service's webhook/API documentation (via WebFetch)
13+
2. Read every trigger file, provider handler, and registry entry
14+
3. Cross-reference against the API docs and Sim conventions
15+
4. Report all issues grouped by severity (critical, warning, suggestion)
16+
5. Fix all issues after reporting them
17+
18+
## Step 1: Gather All Files
19+
20+
Read **every** file for the trigger — do not skip any:
21+
22+
```
23+
apps/sim/triggers/{service}/ # All trigger files, utils.ts, index.ts
24+
apps/sim/lib/webhooks/providers/{service}.ts # Provider handler (if exists)
25+
apps/sim/lib/webhooks/providers/registry.ts # Handler registry
26+
apps/sim/triggers/registry.ts # Trigger registry
27+
apps/sim/blocks/blocks/{service}.ts # Block definition (trigger wiring)
28+
```
29+
30+
Also read for reference:
31+
```
32+
apps/sim/lib/webhooks/providers/types.ts # WebhookProviderHandler interface
33+
apps/sim/lib/webhooks/providers/utils.ts # Shared helpers (createHmacVerifier, etc.)
34+
apps/sim/lib/webhooks/provider-subscription-utils.ts # Subscription helpers
35+
apps/sim/lib/webhooks/processor.ts # Central webhook processor
36+
```
37+
38+
## Step 2: Pull API Documentation
39+
40+
Fetch the service's official webhook documentation. This is the **source of truth** for:
41+
- Webhook event types and payload shapes
42+
- Signature/auth verification method (HMAC algorithm, header names, secret format)
43+
- Challenge/verification handshake requirements
44+
- Webhook subscription API (create/delete endpoints, if applicable)
45+
- Retry behavior and delivery guarantees
46+
47+
## Step 3: Validate Trigger Definitions
48+
49+
### utils.ts
50+
- [ ] `{service}TriggerOptions` lists all trigger IDs accurately
51+
- [ ] `{service}SetupInstructions` provides clear, correct steps for the service
52+
- [ ] `build{Service}ExtraFields` includes relevant filter/config fields with correct `condition`
53+
- [ ] Output builders expose all meaningful fields from the webhook payload
54+
- [ ] Output builders do NOT use `optional: true` or `items` (tool-output-only features)
55+
- [ ] Nested output objects correctly model the payload structure
56+
57+
### Trigger Files
58+
- [ ] Exactly one primary trigger has `includeDropdown: true`
59+
- [ ] All secondary triggers do NOT have `includeDropdown`
60+
- [ ] All triggers use `buildTriggerSubBlocks` helper (not hand-rolled subBlocks)
61+
- [ ] Every trigger's `id` matches the convention `{service}_{event_name}`
62+
- [ ] Every trigger's `provider` matches the service name used in the handler registry
63+
- [ ] `index.ts` barrel exports all triggers
64+
65+
### Trigger ↔ Provider Alignment (CRITICAL)
66+
- [ ] Every trigger ID referenced in `matchEvent` logic exists in `{service}TriggerOptions`
67+
- [ ] Event matching logic in the provider correctly maps trigger IDs to service event types
68+
- [ ] Event matching logic in `is{Service}EventMatch` (if exists) correctly identifies events per the API docs
69+
70+
## Step 4: Validate Provider Handler
71+
72+
### Auth Verification
73+
- [ ] `verifyAuth` correctly validates webhook signatures per the service's documentation
74+
- [ ] HMAC algorithm matches (SHA-1, SHA-256, SHA-512)
75+
- [ ] Signature header name matches the API docs exactly
76+
- [ ] Signature format is handled (raw hex, `sha256=` prefix, base64, etc.)
77+
- [ ] Uses `safeCompare` for timing-safe comparison (no `===`)
78+
- [ ] If `webhookSecret` is required, handler rejects when it's missing (fail-closed)
79+
- [ ] Signature is computed over raw body (not parsed JSON)
80+
81+
### Event Matching
82+
- [ ] `matchEvent` returns `boolean` (not `NextResponse` or other values)
83+
- [ ] Challenge/verification events are excluded from matching (e.g., `endpoint.url_validation`)
84+
- [ ] When `triggerId` is a generic webhook ID, all events pass through
85+
- [ ] When `triggerId` is specific, only matching events pass
86+
- [ ] Event matching logic uses dynamic `await import()` for trigger utils (avoids circular deps)
87+
88+
### formatInput (CRITICAL)
89+
- [ ] Every key in the `formatInput` return matches a key in the trigger `outputs` schema
90+
- [ ] Every key in the trigger `outputs` schema is populated by `formatInput`
91+
- [ ] No extra undeclared keys that users can't discover in the UI
92+
- [ ] No wrapper objects (`webhook: { ... }`, `{service}: { ... }`)
93+
- [ ] Nested output paths exist at the correct depth (e.g., `resource.id` actually has `resource: { id: ... }`)
94+
- [ ] `null` is used for missing optional fields (not empty strings or empty objects)
95+
- [ ] Returns `{ input: { ... } }` — not a bare object
96+
97+
### Idempotency
98+
- [ ] `extractIdempotencyId` returns a stable, unique key per delivery
99+
- [ ] Uses provider-specific delivery IDs when available (e.g., `X-Request-Id`, `Linear-Delivery`, `svix-id`)
100+
- [ ] Falls back to content-based ID (e.g., `${type}:${id}`) when no delivery header exists
101+
- [ ] Does NOT include timestamps in the idempotency key (would break dedup on retries)
102+
103+
### Challenge Handling (if applicable)
104+
- [ ] `handleChallenge` correctly implements the service's URL verification handshake
105+
- [ ] Returns the expected response format per the API docs
106+
- [ ] Env-backed secrets are resolved via `resolveEnvVarsInObject` if needed
107+
108+
## Step 5: Validate Automatic Subscription Lifecycle
109+
110+
If the service supports programmatic webhook creation:
111+
112+
### createSubscription
113+
- [ ] Calls the correct API endpoint to create a webhook
114+
- [ ] Sends the correct event types/filters
115+
- [ ] Passes the notification URL from `getNotificationUrl(ctx.webhook)`
116+
- [ ] Returns `{ providerConfigUpdates: { externalId } }` with the external webhook ID
117+
- [ ] Throws on failure (orchestration handles rollback)
118+
- [ ] Provides user-friendly error messages (401 → "Invalid API Key", etc.)
119+
120+
### deleteSubscription
121+
- [ ] Calls the correct API endpoint to delete the webhook
122+
- [ ] Handles 404 gracefully (webhook already deleted)
123+
- [ ] Never throws — catches errors and logs non-fatally
124+
- [ ] Skips gracefully when `apiKey` or `externalId` is missing
125+
126+
### Orchestration Isolation
127+
- [ ] NO provider-specific logic in `route.ts`, `provider-subscriptions.ts`, or `deploy.ts`
128+
- [ ] All subscription logic lives on the handler (`createSubscription`/`deleteSubscription`)
129+
130+
## Step 6: Validate Registration and Block Wiring
131+
132+
### Trigger Registry (`triggers/registry.ts`)
133+
- [ ] All triggers are imported and registered
134+
- [ ] Registry keys match trigger IDs exactly
135+
- [ ] No orphaned entries (triggers that don't exist)
136+
137+
### Provider Handler Registry (`providers/registry.ts`)
138+
- [ ] Handler is imported and registered (if handler exists)
139+
- [ ] Registry key matches the `provider` field on the trigger configs
140+
- [ ] Entries are in alphabetical order
141+
142+
### Block Wiring (`blocks/blocks/{service}.ts`)
143+
- [ ] Block has `triggers.enabled: true`
144+
- [ ] `triggers.available` lists all trigger IDs
145+
- [ ] All trigger subBlocks are spread into `subBlocks`: `...getTrigger('id').subBlocks`
146+
- [ ] No trigger IDs in `triggers.available` that aren't in the registry
147+
- [ ] No trigger subBlocks spread that aren't in `triggers.available`
148+
149+
## Step 7: Validate Security
150+
151+
- [ ] Webhook secrets are never logged (not even at debug level)
152+
- [ ] Auth verification runs before any event processing
153+
- [ ] No secret comparison uses `===` (must use `safeCompare` or `crypto.timingSafeEqual`)
154+
- [ ] Timestamp/replay protection is reasonable (not too tight for retries, not too loose for security)
155+
- [ ] Raw body is used for signature verification (not re-serialized JSON)
156+
157+
## Step 8: Report and Fix
158+
159+
### Report Format
160+
161+
Group findings by severity:
162+
163+
**Critical** (runtime errors, security issues, or data loss):
164+
- Wrong HMAC algorithm or header name
165+
- `formatInput` keys don't match trigger `outputs`
166+
- Missing `verifyAuth` when the service sends signed webhooks
167+
- `matchEvent` returns non-boolean values
168+
- Provider-specific logic leaking into shared orchestration files
169+
- Trigger IDs mismatch between trigger files, registry, and block
170+
- `createSubscription` calling wrong API endpoint
171+
- Auth comparison using `===` instead of `safeCompare`
172+
173+
**Warning** (convention violations or usability issues):
174+
- Missing `extractIdempotencyId` when the service provides delivery IDs
175+
- Timestamps in idempotency keys (breaks dedup on retries)
176+
- Missing challenge handling when the service requires URL verification
177+
- Output schema missing fields that `formatInput` returns (undiscoverable data)
178+
- Overly tight timestamp skew window that rejects legitimate retries
179+
- `matchEvent` not filtering challenge/verification events
180+
- Setup instructions missing important steps
181+
182+
**Suggestion** (minor improvements):
183+
- More specific output field descriptions
184+
- Additional output fields that could be exposed
185+
- Better error messages in `createSubscription`
186+
- Logging improvements
187+
188+
### Fix All Issues
189+
190+
After reporting, fix every **critical** and **warning** issue. Apply **suggestions** where they don't add unnecessary complexity.
191+
192+
### Validation Output
193+
194+
After fixing, confirm:
195+
1. `bun run type-check` passes
196+
2. Re-read all modified files to verify fixes are correct
197+
3. Provider handler tests pass (if they exist): `bun test {service}`
198+
199+
## Checklist Summary
200+
201+
- [ ] Read all trigger files, provider handler, types, registries, and block
202+
- [ ] Pulled and read official webhook/API documentation
203+
- [ ] Validated trigger definitions: options, instructions, extra fields, outputs
204+
- [ ] Validated primary/secondary trigger distinction (`includeDropdown`)
205+
- [ ] Validated provider handler: auth, matchEvent, formatInput, idempotency
206+
- [ ] Validated output alignment: every `outputs` key ↔ every `formatInput` key
207+
- [ ] Validated subscription lifecycle: createSubscription, deleteSubscription, no shared-file edits
208+
- [ ] Validated registration: trigger registry, handler registry, block wiring
209+
- [ ] Validated security: safe comparison, no secret logging, replay protection
210+
- [ ] Reported all issues grouped by severity
211+
- [ ] Fixed all critical and warning issues
212+
- [ ] `bun run type-check` passes after fixes

0 commit comments

Comments
 (0)