fix(security): add webhook HMAC signature verification for Airtable, HubSpot, and Webflow#4550
fix(security): add webhook HMAC signature verification for Airtable, HubSpot, and Webflow#4550waleedlatif1 wants to merge 9 commits into
Conversation
…HubSpot, and Webflow - Airtable: verify X-Airtable-Content-MAC via HMAC-SHA256 of raw body; strip hmac-sha256= prefix before comparing; fail-open with warning when macSecretBase64 is absent (existing webhooks predating secret storage) - HubSpot: verify X-HubSpot-Signature via SHA-256(clientSecret + body) per v1 spec - Webflow: verify X-Webflow-Signature via HMAC-SHA256(secretKey, timestamp:body); store secretKey from webhook creation response; add 5-min replay protection; fail-open when secretKey absent for pre-existing webhooks - tool-schemas-v1.ts: linter formatting cleanup (computed keys)
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Persists provider secrets needed for verification ( Reviewed by Cursor Bugbot for commit ee3290a. Configure here. |
Greptile SummaryThis PR adds HMAC webhook signature verification (
Confidence Score: 4/5The Airtable and HubSpot implementations are correct and safe to merge. The Webflow implementation has one incorrect fallback that would silently store the wrong signing key, causing every Webflow webhook HMAC check to fail when the API response omits secretKey. Airtable and HubSpot verification logic is sound and consistent. In the Webflow handler, apps/sim/lib/webhooks/providers/webflow.ts — specifically the secretKey fallback in createSubscription Important Files Changed
Sequence DiagramsequenceDiagram
participant W as Webhook Source
participant R as Webhook Route
participant V as verifyAuth
participant DB as providerConfig
W->>R: POST /api/webhooks/trigger/:path
R->>DB: Load providerConfig (macSecretBase64 / clientSecret / secretKey)
R->>V: verifyAuth(request, rawBody, providerConfig)
alt Airtable
V->>V: macSecretBase64 absent? → return null (fail-open)
V->>V: HMAC-SHA256(base64decode(macSecretBase64), rawBody)
V->>V: safeCompare(computed, X-Airtable-Content-MAC)
else HubSpot
V->>V: clientSecret absent? → 401
V->>V: SHA-256(clientSecret + rawBody)
V->>V: safeCompare(computed, X-HubSpot-Signature)
else Webflow
V->>V: secretKey absent? → 401
V->>V: Check x-webflow-timestamp replay window
V->>V: HMAC-SHA256(secretKey, timestamp:rawBody)
V->>V: safeCompare(computed, x-webflow-signature)
end
V-->>R: null (pass) or NextResponse(401) (reject)
R->>W: 200 OK or 401 Unauthorized
Reviews (4): Last reviewed commit: "fix(webhooks): replace error as Error ca..." | Re-trigger Greptile |
… hmac encoding - webflow: compare Date.now()/1000 against x-webflow-timestamp (both in Unix seconds) — the original Date.now() - ts comparison always exceeded the 300 000ms threshold, rejecting every valid webhook - airtable: use utf8 encoding in HMAC update instead of ascii — ascii silently mangles bytes above 127, producing an incorrect digest for non-ASCII payloads - hubspot: add TSDoc comment clarifying v1 signature scheme is intentional for CRM subscriptions
|
@greptile |
|
@cursor review |
… artifacts - webflow: wrap HMAC computation and safeCompare in try-catch to return a clean 401 instead of an unhandled 500 when secretKey is a non-string truthy value (mirrors the defensive pattern already used by Airtable and HubSpot in the same PR) - airtable: remove CRITICAL_TRACE/TRACE/DEBUG log prefixes, redundant "Error logging handled by logging session" comments, and other stale implementation notes that pre-date the refactor
…s milliseconds Per official Webflow docs (example value 1722370035277, 13 digits), the header is Unix milliseconds. Comparing Date.now()/1000 against a ms timestamp produced a permanently-negative delta, making replay protection always pass. Reverted to Date.now() - ts > 5 * 60 * 1000, matching Webflow's own reference implementation. Also normalized caught error via toError().
|
@cursor review |
|
@greptile |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 6ccc7b4. Configure here.
fix(security): store Webflow signing key at subscription creation time - Remove all non-TSDoc inline comments from airtable, hubspot, and webflow providers - Fix Webflow OAuth webhook signature verification gap: secretKey was never stored for OAuth-created webhooks (Webflow returns secretKey only for site-token webhooks). Now resolved at createSubscription time via responseBody.secretKey ?? env.WEBFLOW_CLIENT_SECRET ?? null, so verifyAuth has a single clean code path identical to Stripe and HubSpot - Change Webflow verifyAuth from fail-open to fail-closed on missing secretKey - Replace error as Error cast with toError() in Webflow createSubscription catch - Add inline comment noting x-webflow-timestamp is Unix milliseconds
…viders Replace all (error as Error) casts with toError() from @sim/utils/errors in airtable and hubspot providers, per global standard in CLAUDE.md. Adds toError import to airtable.ts.
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ee3290a. Configure here.

Summary
verifyAuthusing HMAC-SHA256 of the raw body keyed by the base64-decodedmacSecretBase64from providerConfig; compare against thehmac-sha256=<hex>prefix inX-Airtable-Content-MAC; fail-open with a warning whenmacSecretBase64is absent (existing webhooks created before secret storage — no migration path since Airtable only returns the secret once at creation)verifyAuthusing SHA-256(clientSecret+ raw body) compared againstX-HubSpot-Signatureper the v1 CRM webhook spec;clientSecretis already a required subBlock field in all HubSpot triggers so no existing webhooks breakverifyAuthusing HMAC-SHA256(secretKey,${timestamp}:${rawBody}) compared againstX-Webflow-Signature; store thesecretKeyfrom the webhook creation API response in providerConfig; add 5-minute replay protection viax-webflow-timestamp; fail-open whensecretKeyis absent for pre-existing webhookssafeComparefrom@sim/security/comparefor constant-time comparison and fail-closed on missing secrets (except for migration backwards-compat cases which fail-open with a warning log)Type of Change
Testing
Tested manually. Algorithms validated against official provider docs: Airtable webhooks overview, HubSpot webhook validation docs (v1 confirmed for CRM object subscriptions), Webflow Data API webhook docs. Airtable
hmac-sha256=prefix confirmed from live header examples in community threads.Checklist