Skip to content

Commit 28f3f1c

Browse files
committed
fix(security): address review comments on SSRF PR
- Remove Promise.race timeout pattern to avoid unhandled rejections (http.request timeout is sufficient for webhook delivery) - Use safeCompare in verifyCronAuth instead of inline HMAC logic - Strip IPv6 brackets before validateDatabaseHost in Redis route
1 parent aca49ac commit 28f3f1c

File tree

4 files changed

+9
-23
lines changed

4 files changed

+9
-23
lines changed

apps/sim/app/api/tools/redis/execute/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ export async function POST(request: NextRequest) {
2626
const { url, command, args } = RequestSchema.parse(body)
2727

2828
const parsedUrl = new URL(url)
29-
const hostValidation = await validateDatabaseHost(parsedUrl.hostname, 'host')
29+
const hostname =
30+
parsedUrl.hostname.startsWith('[') && parsedUrl.hostname.endsWith(']')
31+
? parsedUrl.hostname.slice(1, -1)
32+
: parsedUrl.hostname
33+
const hostValidation = await validateDatabaseHost(hostname, 'host')
3034
if (!hostValidation.isValid) {
3135
return NextResponse.json({ error: hostValidation.error }, { status: 400 })
3236
}

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ async function testWebhook(subscription: typeof workspaceNotificationSubscriptio
137137
}
138138

139139
try {
140-
const fetchPromise = secureFetchWithValidation(
140+
const response = await secureFetchWithValidation(
141141
webhookConfig.url,
142142
{
143143
method: 'POST',
@@ -147,12 +147,6 @@ async function testWebhook(subscription: typeof workspaceNotificationSubscriptio
147147
},
148148
'webhookUrl'
149149
)
150-
151-
const timeoutPromise = new Promise<never>((_resolve, reject) => {
152-
setTimeout(() => reject(new Error('Request timeout')), 10000)
153-
})
154-
155-
const response = await Promise.race([fetchPromise, timeoutPromise])
156150
const responseBody = await response.text().catch(() => '')
157151

158152
return {

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ async function deliverWebhook(
209209
}
210210

211211
try {
212-
const fetchPromise = secureFetchWithValidation(
212+
const response = await secureFetchWithValidation(
213213
webhookConfig.url,
214214
{
215215
method: 'POST',
@@ -220,12 +220,6 @@ async function deliverWebhook(
220220
'webhookUrl'
221221
)
222222

223-
const timeoutPromise = new Promise<never>((_resolve, reject) => {
224-
setTimeout(() => reject(new Error('Request timeout')), 30000)
225-
})
226-
227-
const response = await Promise.race([fetchPromise, timeoutPromise])
228-
229223
return {
230224
success: response.ok,
231225
status: response.status,

apps/sim/lib/auth/internal.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { createHmac, timingSafeEqual } from 'crypto'
21
import { createLogger } from '@sim/logger'
32
import { jwtVerify, SignJWT } from 'jose'
43
import { type NextRequest, NextResponse } from 'next/server'
54
import { env } from '@/lib/core/config/env'
5+
import { safeCompare } from '@/lib/core/security/encryption'
66

77
const logger = createLogger('CronAuth')
88

@@ -82,13 +82,7 @@ export function verifyCronAuth(request: NextRequest, context?: string): NextResp
8282

8383
const authHeader = request.headers.get('authorization')
8484
const expectedAuth = `Bearer ${env.CRON_SECRET}`
85-
const key = 'verifyCronAuth'
86-
const isValid =
87-
authHeader !== null &&
88-
timingSafeEqual(
89-
createHmac('sha256', key).update(authHeader).digest(),
90-
createHmac('sha256', key).update(expectedAuth).digest()
91-
)
85+
const isValid = authHeader !== null && safeCompare(authHeader, expectedAuth)
9286
if (!isValid) {
9387
const contextInfo = context ? ` for ${context}` : ''
9488
logger.warn(`Unauthorized CRON access attempt${contextInfo}`, {

0 commit comments

Comments
 (0)