Skip to content

Commit 291f79c

Browse files
committed
fix(triggers): harden Zoom webhook security per PR review
- verifyAuth now fails closed (401) when secretToken is missing - handleChallenge DB query filters by provider='zoom' to avoid cross-provider leaks - handleChallenge verifies x-zm-signature before responding to prevent HMAC oracle
1 parent d2f42bf commit 291f79c

File tree

1 file changed

+19
-3
lines changed
  • apps/sim/lib/webhooks/providers

1 file changed

+19
-3
lines changed

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ export const zoomHandler: WebhookProviderHandler = {
5050
verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) {
5151
const secretToken = providerConfig.secretToken as string | undefined
5252
if (!secretToken) {
53-
return null
53+
logger.warn(
54+
`[${requestId}] Zoom webhook missing secretToken in providerConfig — rejecting request`
55+
)
56+
return new NextResponse('Unauthorized - Zoom secret token not configured', { status: 401 })
5457
}
5558

5659
const signature = request.headers.get('x-zm-signature')
@@ -98,7 +101,7 @@ export const zoomHandler: WebhookProviderHandler = {
98101
* Zoom sends an `endpoint.url_validation` event with a `plainToken` that must
99102
* be hashed with the app's secret token and returned alongside the original token.
100103
*/
101-
async handleChallenge(body: unknown, _request: NextRequest, requestId: string, path: string) {
104+
async handleChallenge(body: unknown, request: NextRequest, requestId: string, path: string) {
102105
const obj = body as Record<string, unknown> | null
103106
if (obj?.event !== 'endpoint.url_validation') {
104107
return null
@@ -118,7 +121,9 @@ export const zoomHandler: WebhookProviderHandler = {
118121
const webhooks = await db
119122
.select()
120123
.from(webhook)
121-
.where(and(eq(webhook.path, path), eq(webhook.isActive, true)))
124+
.where(
125+
and(eq(webhook.path, path), eq(webhook.provider, 'zoom'), eq(webhook.isActive, true))
126+
)
122127
if (webhooks.length > 0) {
123128
const config = webhooks[0].providerConfig as Record<string, unknown> | null
124129
secretToken = (config?.secretToken as string) || ''
@@ -135,6 +140,17 @@ export const zoomHandler: WebhookProviderHandler = {
135140
return null
136141
}
137142

143+
// Verify the challenge request's signature to prevent HMAC oracle attacks
144+
const signature = request.headers.get('x-zm-signature')
145+
const timestamp = request.headers.get('x-zm-request-timestamp')
146+
if (signature && timestamp) {
147+
const rawBody = JSON.stringify(body)
148+
if (!validateZoomSignature(secretToken, signature, timestamp, rawBody)) {
149+
logger.warn(`[${requestId}] Zoom challenge request failed signature verification`)
150+
return null
151+
}
152+
}
153+
138154
const hashForValidate = crypto
139155
.createHmac('sha256', secretToken)
140156
.update(plainToken)

0 commit comments

Comments
 (0)