Skip to content

fix(security): add webhook HMAC signature verification for Airtable, HubSpot, and Webflow#4550

Open
waleedlatif1 wants to merge 4 commits intostagingfrom
fix/security-webhook-hmac
Open

fix(security): add webhook HMAC signature verification for Airtable, HubSpot, and Webflow#4550
waleedlatif1 wants to merge 4 commits intostagingfrom
fix/security-webhook-hmac

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • Airtable: implement verifyAuth using HMAC-SHA256 of the raw body keyed by the base64-decoded macSecretBase64 from providerConfig; compare against the hmac-sha256=<hex> prefix in X-Airtable-Content-MAC; fail-open with a warning when macSecretBase64 is absent (existing webhooks created before secret storage — no migration path since Airtable only returns the secret once at creation)
  • HubSpot: implement verifyAuth using SHA-256(clientSecret + raw body) compared against X-HubSpot-Signature per the v1 CRM webhook spec; clientSecret is already a required subBlock field in all HubSpot triggers so no existing webhooks break
  • Webflow: implement verifyAuth using HMAC-SHA256(secretKey, ${timestamp}:${rawBody}) compared against X-Webflow-Signature; store the secretKey from the webhook creation API response in providerConfig; add 5-minute replay protection via x-webflow-timestamp; fail-open when secretKey is absent for pre-existing webhooks
  • All three implementations use safeCompare from @sim/security/compare for 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

  • Bug fix

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

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…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)
@vercel
Copy link
Copy Markdown

vercel Bot commented May 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 11, 2026 1:49am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 10, 2026

PR Summary

High Risk
Adds/changes webhook request authentication logic; mistakes could incorrectly reject legitimate webhooks or allow spoofed requests, impacting workflow executions.

Overview
Secures webhook ingestion by adding per-provider request signature verification via new verifyAuth handlers for airtable, hubspot, and webflow.

Airtable now verifies X-Airtable-Content-MAC (HMAC-SHA256 of raw body using stored macSecretBase64, fail-open when missing) and persists macSecretBase64 when creating the webhook; HubSpot now verifies X-HubSpot-Signature using the v1 SHA-256 scheme and fails closed when clientSecret is absent; Webflow now verifies x-webflow-signature with timestamped HMAC plus 5-minute replay protection, stores secretKey at subscription creation, and fails open when legacy webhooks lack the secret.

Also normalizes the generated TOOL_RUNTIME_SCHEMAS object keys in tool-schemas-v1.ts (removing bracketed string keys) without changing schema content.

Reviewed by Cursor Bugbot for commit 6ccc7b4. Configure here.

Comment thread apps/sim/lib/webhooks/providers/airtable.ts Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 10, 2026

Greptile Summary

This PR adds HMAC-based webhook signature verification to three providers (Airtable, HubSpot, Webflow) that previously had no verifyAuth implementation. All three handlers use safeCompare for constant-time comparison and follow provider-specific signing specs.

  • Airtable: HMAC-SHA256 with a base64-decoded macSecretBase64 key against the hmac-sha256= prefixed header; fail-open when macSecretBase64 is absent (one-time creation secret, no migration path for existing webhooks).
  • HubSpot: SHA-256 of clientSecret + rawBody against X-HubSpot-Signature; fail-closed since clientSecret is a required sub-block field; TSDoc comment explains the intentional v1 scheme choice.
  • Webflow: HMAC-SHA256 of timestamp:rawBody with 5-minute replay protection; secretKey stored from the Webflow creation response; fail-open for pre-existing webhooks. The tool-schemas-v1.ts change is an unrelated cosmetic cleanup.

Confidence Score: 5/5

Safe to merge — the three new verifyAuth implementations are self-contained, follow provider-specific signing specs, and use constant-time comparison throughout.

All three handlers correctly compute and compare HMAC/hash signatures using Node's built-in crypto module. Constant-time comparison via safeCompare is used in every path. Fail-open/fail-closed decisions match the PR description and documented backwards-compat constraints. No functional regressions found in existing subscription creation or deletion logic.

No files require special attention — the security-critical verification paths in all three provider files look correct.

Important Files Changed

Filename Overview
apps/sim/lib/webhooks/providers/airtable.ts Adds verifyAuth with HMAC-SHA256 validation using base64-decoded macSecretBase64; fail-open on missing secret; stores macSecretBase64 from creation response in providerConfig.
apps/sim/lib/webhooks/providers/hubspot.ts Adds verifyAuth with SHA-256(clientSecret + rawBody) against X-HubSpot-Signature (v1 scheme); fail-closed on missing secret; TSDoc comment explains intentional v1 choice.
apps/sim/lib/webhooks/providers/webflow.ts Adds verifyAuth with HMAC-SHA256(secretKey, timestamp:rawBody) and 5-minute replay protection; secretKey stored from creation response; fail-open on missing secret; timestamp treated as milliseconds.
apps/sim/lib/copilot/generated/tool-schemas-v1.ts Cosmetic cleanup: converts computed property keys from bracket notation to bare identifier notation; no functional change.

Reviews (3): Last reviewed commit: "fix(security): correct Webflow timestamp..." | Re-trigger Greptile

Comment thread apps/sim/lib/webhooks/providers/webflow.ts
Comment thread apps/sim/lib/webhooks/providers/hubspot.ts
… 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
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/webhooks/providers/webflow.ts Outdated
… 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().
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

✅ 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.

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