Skip to content

Commit 3d4be6d

Browse files
waleedlatif1claude
andcommitted
refactor(security): consolidate HMAC-SHA256 primitives into @sim/security
Adds hmacSha256Hex and hmacSha256Base64 to @sim/security/hmac and migrates 15 webhook providers plus 5 other hot paths (deployment token signing, outbound webhook requests, workspace notification delivery, notification test route, Shopify OAuth callback) off bare `createHmac` calls. Secret parameter accepts `string | Buffer` to cover base64-decoded Svix-style secrets (Resend) and MS Teams' HMAC scheme. AWS SigV4 signing in S3 and Textract tools intentionally retains direct `createHmac` usage — its multi-step key derivation chain doesn't fit a generic helper. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 9240561 commit 3d4be6d

23 files changed

Lines changed: 112 additions & 55 deletions

File tree

apps/sim/app/api/auth/oauth2/callback/shopify/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import crypto from 'crypto'
21
import { createLogger } from '@sim/logger'
32
import { safeCompare } from '@sim/security/compare'
3+
import { hmacSha256Hex } from '@sim/security/hmac'
44
import { type NextRequest, NextResponse } from 'next/server'
55
import { getSession } from '@/lib/auth'
66
import { env } from '@/lib/core/config/env'
@@ -35,7 +35,7 @@ function validateHmac(searchParams: URLSearchParams, clientSecret: string): bool
3535
.map((key) => `${key}=${params[key]}`)
3636
.join('&')
3737

38-
const generatedHmac = crypto.createHmac('sha256', clientSecret).update(message).digest('hex')
38+
const generatedHmac = hmacSha256Hex(message, clientSecret)
3939

4040
return safeCompare(hmac, generatedHmac)
4141
}

apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/test/route.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { createHmac } from 'crypto'
21
import { db } from '@sim/db'
32
import { account, workspaceNotificationSubscription } from '@sim/db/schema'
43
import { createLogger } from '@sim/logger'
4+
import { hmacSha256Hex } from '@sim/security/hmac'
55
import { toError } from '@sim/utils/errors'
66
import { generateId } from '@sim/utils/id'
77
import { and, eq } from 'drizzle-orm'
@@ -36,9 +36,7 @@ interface SlackConfig {
3636

3737
function generateSignature(secret: string, timestamp: number, body: string): string {
3838
const signatureBase = `${timestamp}.${body}`
39-
const hmac = createHmac('sha256', secret)
40-
hmac.update(signatureBase)
41-
return hmac.digest('hex')
39+
return hmacSha256Hex(signatureBase, secret)
4240
}
4341

4442
function buildTestPayload(subscription: typeof workspaceNotificationSubscription.$inferSelect) {

apps/sim/background/workspace-notification-delivery.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { createHmac } from 'crypto'
21
import { db, workflowExecutionLogs } from '@sim/db'
32
import {
43
account,
54
workspaceNotificationDelivery,
65
workspaceNotificationSubscription,
76
} from '@sim/db/schema'
87
import { createLogger } from '@sim/logger'
8+
import { hmacSha256Hex } from '@sim/security/hmac'
99
import { toError } from '@sim/utils/errors'
1010
import { formatDuration } from '@sim/utils/formatting'
1111
import { generateId } from '@sim/utils/id'
@@ -62,9 +62,7 @@ interface NotificationPayload {
6262

6363
function generateSignature(secret: string, timestamp: number, body: string): string {
6464
const signatureBase = `${timestamp}.${body}`
65-
const hmac = createHmac('sha256', secret)
66-
hmac.update(signatureBase)
67-
return hmac.digest('hex')
65+
return hmacSha256Hex(signatureBase, secret)
6866
}
6967

7068
async function buildPayload(

apps/sim/lib/core/security/deployment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { createHmac } from 'crypto'
21
import { safeCompare } from '@sim/security/compare'
32
import { sha256Hex } from '@sim/security/hash'
3+
import { hmacSha256Hex } from '@sim/security/hmac'
44
import type { NextRequest, NextResponse } from 'next/server'
55
import { env } from '@/lib/core/config/env'
66
import { isDev } from '@/lib/core/config/feature-flags'
@@ -11,7 +11,7 @@ import { isDev } from '@/lib/core/config/feature-flags'
1111
*/
1212

1313
function signPayload(payload: string): string {
14-
return createHmac('sha256', env.BETTER_AUTH_SECRET).update(payload).digest('hex')
14+
return hmacSha256Hex(payload, env.BETTER_AUTH_SECRET)
1515
}
1616

1717
function passwordSlot(encryptedPassword?: string | null): string {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import crypto from 'crypto'
21
import { createLogger } from '@sim/logger'
32
import { safeCompare } from '@sim/security/compare'
3+
import { hmacSha256Hex } from '@sim/security/hmac'
44
import { generateId } from '@sim/utils/id'
55
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
66
import type {
@@ -24,7 +24,7 @@ function validateAshbySignature(secretToken: string, signature: string, body: st
2424
return false
2525
}
2626
const providedSignature = signature.substring(7)
27-
const computedHash = crypto.createHmac('sha256', secretToken).update(body, 'utf8').digest('hex')
27+
const computedHash = hmacSha256Hex(body, secretToken)
2828
return safeCompare(computedHash, providedSignature)
2929
} catch (error) {
3030
logger.error('Error validating Ashby signature:', error)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import crypto from 'crypto'
21
import { createLogger } from '@sim/logger'
32
import { safeCompare } from '@sim/security/compare'
3+
import { hmacSha256Hex } from '@sim/security/hmac'
44
import { toError } from '@sim/utils/errors'
55
import { NextResponse } from 'next/server'
66
import { getBaseUrl } from '@/lib/core/utils/urls'
@@ -29,7 +29,7 @@ function validateAttioSignature(secret: string, signature: string, body: string)
2929
})
3030
return false
3131
}
32-
const computedHash = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')
32+
const computedHash = hmacSha256Hex(body, secret)
3333
logger.debug('Attio signature comparison', {
3434
computedSignature: `${computedHash.substring(0, 10)}...`,
3535
providedSignature: `${signature.substring(0, 10)}...`,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import crypto from 'crypto'
21
import { createLogger } from '@sim/logger'
32
import { safeCompare } from '@sim/security/compare'
3+
import { hmacSha256Hex } from '@sim/security/hmac'
44
import type { WebhookProviderHandler } from '@/lib/webhooks/providers/types'
55
import { createHmacVerifier } from '@/lib/webhooks/providers/utils'
66

@@ -22,7 +22,7 @@ function validateCalcomSignature(secret: string, signature: string, body: string
2222
} else {
2323
providedSignature = signature
2424
}
25-
const computedHash = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')
25+
const computedHash = hmacSha256Hex(body, secret)
2626
logger.debug('Cal.com signature comparison', {
2727
computedSignature: `${computedHash.substring(0, 10)}...`,
2828
providedSignature: `${providedSignature.substring(0, 10)}...`,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import crypto from 'crypto'
21
import { createLogger } from '@sim/logger'
32
import { safeCompare } from '@sim/security/compare'
3+
import { hmacSha256Hex } from '@sim/security/hmac'
44
import type {
55
FormatInputContext,
66
FormatInputResult,
@@ -20,7 +20,7 @@ function validateCirclebackSignature(secret: string, signature: string, body: st
2020
})
2121
return false
2222
}
23-
const computedHash = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')
23+
const computedHash = hmacSha256Hex(body, secret)
2424
logger.debug('Circleback signature comparison', {
2525
computedSignature: `${computedHash.substring(0, 10)}...`,
2626
providedSignature: `${signature.substring(0, 10)}...`,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import crypto from 'crypto'
21
import { createLogger } from '@sim/logger'
32
import { safeCompare } from '@sim/security/compare'
3+
import { hmacSha256Hex } from '@sim/security/hmac'
44
import type {
55
FormatInputContext,
66
FormatInputResult,
@@ -27,7 +27,7 @@ function validateFirefliesSignature(secret: string, signature: string, body: str
2727
return false
2828
}
2929
const providedSignature = signature.substring(7)
30-
const computedHash = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')
30+
const computedHash = hmacSha256Hex(body, secret)
3131
logger.debug('Fireflies signature comparison', {
3232
computedSignature: `${computedHash.substring(0, 10)}...`,
3333
providedSignature: `${providedSignature.substring(0, 10)}...`,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import crypto from 'crypto'
21
import { createLogger } from '@sim/logger'
32
import { safeCompare } from '@sim/security/compare'
3+
import { hmacSha256Hex } from '@sim/security/hmac'
44
import type {
55
EventMatchContext,
66
FormatInputContext,
@@ -25,7 +25,7 @@ function validateGreenhouseSignature(secretKey: string, signature: string, body:
2525
return false
2626
}
2727
const providedDigest = signature.substring(prefix.length)
28-
const computedDigest = crypto.createHmac('sha256', secretKey).update(body, 'utf8').digest('hex')
28+
const computedDigest = hmacSha256Hex(body, secretKey)
2929
return safeCompare(computedDigest, providedDigest)
3030
} catch {
3131
logger.error('Error validating Greenhouse signature')

0 commit comments

Comments
 (0)