Skip to content

Commit 49b9503

Browse files
committed
fix(security): correct webflow timestamp unit comparison and airtable 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
1 parent 6bb4789 commit 49b9503

3 files changed

Lines changed: 9 additions & 4 deletions

File tree

apps/sim/lib/webhooks/providers/airtable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ export const airtableHandler: WebhookProviderHandler = {
471471
const secretBytes = Buffer.from(macSecretBase64, 'base64')
472472
const computedHex = crypto
473473
.createHmac('sha256', secretBytes)
474-
.update(rawBody, 'ascii')
474+
.update(rawBody, 'utf8')
475475
.digest('hex')
476476

477477
if (!safeCompare(computedHex, providedHex)) {

apps/sim/lib/webhooks/providers/hubspot.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ export const hubspotHandler: WebhookProviderHandler = {
3030
}
3131

3232
try {
33-
// HubSpot v1 signature: SHA-256 hash of (clientSecret + requestBody)
33+
/**
34+
* HubSpot v1 signature: SHA-256 of (clientSecret + requestBody), verified against X-HubSpot-Signature.
35+
* v1 is intentionally used for CRM webhook subscriptions — v3 requires the full request URL and method,
36+
* which are not available in verifyAuth.
37+
*/
3438
const computedHash = crypto
3539
.createHash('sha256')
3640
.update(clientSecret + rawBody, 'utf8')

apps/sim/lib/webhooks/providers/webflow.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ export const webflowHandler: WebhookProviderHandler = {
3939
return new NextResponse('Unauthorized - Missing Webflow signature headers', { status: 401 })
4040
}
4141

42-
// Replay protection: reject if timestamp is more than 5 minutes old
42+
// Replay protection: reject if timestamp is more than 5 minutes old.
43+
// x-webflow-timestamp is Unix seconds; Date.now() is milliseconds — compare both in seconds.
4344
const ts = Number.parseInt(timestamp, 10)
44-
if (Number.isNaN(ts) || Date.now() - ts > 5 * 60 * 1000) {
45+
if (Number.isNaN(ts) || Date.now() / 1000 - ts > 5 * 60) {
4546
logger.warn(`[${requestId}] Webflow webhook timestamp expired or invalid`)
4647
return new NextResponse('Unauthorized - Webhook timestamp expired', { status: 401 })
4748
}

0 commit comments

Comments
 (0)