Skip to content

Commit f9e487a

Browse files
waleedlatif1claude
andcommitted
fix(secure-fetch): remove abort listener when request settles
The abort listener was registered with `{ once: true }`, which only auto- removes on signal fire — never on success/timeout/error. Webhook drains reuse the same Trigger.dev signal across thousands of chunks/retries, so listeners (each retaining a closed http.ClientRequest) accumulated for the job's lifetime. Wrap resolve/reject in a settle helper that also calls removeEventListener. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 7940e30 commit f9e487a

1 file changed

Lines changed: 26 additions & 10 deletions

File tree

apps/sim/lib/core/security/input-validation.server.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ export async function secureFetchWithPinnedIP(
311311
validateUrlWithDNS(redirectUrl, 'redirectUrl', { allowHttp: options.allowHttp })
312312
.then((validation) => {
313313
if (!validation.isValid) {
314-
reject(new Error(`Redirect blocked: ${validation.error}`))
314+
settledReject(new Error(`Redirect blocked: ${validation.error}`))
315315
return
316316
}
317317
return secureFetchWithPinnedIP(
@@ -322,15 +322,15 @@ export async function secureFetchWithPinnedIP(
322322
)
323323
})
324324
.then((response) => {
325-
if (response) resolve(response)
325+
if (response) settledResolve(response)
326326
})
327-
.catch(reject)
327+
.catch(settledReject)
328328
return
329329
}
330330

331331
if (isRedirectStatus(statusCode) && location && redirectCount >= maxRedirects) {
332332
res.resume()
333-
reject(new Error(`Too many redirects (max: ${maxRedirects})`))
333+
settledReject(new Error(`Too many redirects (max: ${maxRedirects})`))
334334
return
335335
}
336336

@@ -356,7 +356,7 @@ export async function secureFetchWithPinnedIP(
356356
})
357357

358358
res.on('error', (error) => {
359-
reject(error)
359+
settledReject(error)
360360
})
361361

362362
res.on('end', () => {
@@ -372,7 +372,7 @@ export async function secureFetchWithPinnedIP(
372372
}
373373
}
374374

375-
resolve({
375+
settledResolve({
376376
ok: statusCode >= 200 && statusCode < 300,
377377
status: statusCode,
378378
statusText: res.statusMessage || '',
@@ -388,13 +388,29 @@ export async function secureFetchWithPinnedIP(
388388
})
389389
})
390390

391+
let onAbort: (() => void) | null = null
392+
const cleanupAbort = () => {
393+
if (onAbort && options.signal) {
394+
options.signal.removeEventListener('abort', onAbort)
395+
onAbort = null
396+
}
397+
}
398+
const settledResolve: typeof resolve = (value) => {
399+
cleanupAbort()
400+
resolve(value)
401+
}
402+
const settledReject: typeof reject = (reason) => {
403+
cleanupAbort()
404+
reject(reason)
405+
}
406+
391407
req.on('error', (error) => {
392-
reject(error)
408+
settledReject(error)
393409
})
394410

395411
req.on('timeout', () => {
396412
req.destroy()
397-
reject(new Error(`Request timed out after ${requestOptions.timeout}ms`))
413+
settledReject(new Error(`Request timed out after ${requestOptions.timeout}ms`))
398414
})
399415

400416
if (options.signal) {
@@ -403,9 +419,9 @@ export async function secureFetchWithPinnedIP(
403419
reject(options.signal.reason ?? new Error('Aborted'))
404420
return
405421
}
406-
const onAbort = () => {
422+
onAbort = () => {
407423
req.destroy()
408-
reject(options.signal?.reason ?? new Error('Aborted'))
424+
settledReject(options.signal?.reason ?? new Error('Aborted'))
409425
}
410426
options.signal.addEventListener('abort', onAbort, { once: true })
411427
}

0 commit comments

Comments
 (0)