Skip to content

Commit c40c6d7

Browse files
committed
refactor(webhooks): extract shared body-size cap to constants module
Address review feedback: hoist WEBHOOK_MAX_BODY_BYTES into a single lib/webhooks/constants.ts so the trigger receiver and AgentMail route share one source of truth instead of recomputing the env-derived cap (prevents drift). Also drop the redundant request clone when the body stream is null.
1 parent 7a3918d commit c40c6d7

3 files changed

Lines changed: 22 additions & 20 deletions

File tree

apps/sim/app/api/webhooks/agentmail/route.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
agentMailMessageSchema,
2020
webhookSvixHeadersSchema,
2121
} from '@/lib/api/contracts/webhooks'
22-
import { env } from '@/lib/core/config/env'
2322
import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags'
2423
import {
2524
assertContentLengthWithinLimit,
@@ -29,29 +28,27 @@ import {
2928
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
3029
import { executeInboxTask } from '@/lib/mothership/inbox/executor'
3130
import type { AgentMailWebhookPayload, RejectionReason } from '@/lib/mothership/inbox/types'
31+
import { WEBHOOK_MAX_BODY_BYTES } from '@/lib/webhooks/constants'
3232

3333
const logger = createLogger('AgentMailWebhook')
3434

3535
const AUTOMATED_SENDERS = ['mailer-daemon@', 'noreply@', 'no-reply@', 'postmaster@']
3636
const MAX_EMAILS_PER_HOUR = 20
3737

38+
const AGENTMAIL_BODY_LABEL = 'AgentMail webhook body'
39+
3840
/**
3941
* Bound the unauthenticated AgentMail webhook body before buffering it for Svix
4042
* signature verification, so an oversized payload cannot exhaust pod memory.
4143
*/
42-
const AGENTMAIL_MAX_BODY_BYTES =
43-
Number.parseInt(env.WEBHOOK_MAX_REQUEST_BYTES, 10) || 10 * 1024 * 1024
44-
45-
const AGENTMAIL_BODY_LABEL = 'AgentMail webhook body'
46-
4744
async function readAgentMailBody(req: Request): Promise<string> {
48-
assertContentLengthWithinLimit(req.headers, AGENTMAIL_MAX_BODY_BYTES, AGENTMAIL_BODY_LABEL)
45+
assertContentLengthWithinLimit(req.headers, WEBHOOK_MAX_BODY_BYTES, AGENTMAIL_BODY_LABEL)
4946
const stream = req.body
5047
if (!stream) {
5148
return req.text()
5249
}
5350
const buffer = await readStreamToBufferWithLimit(stream, {
54-
maxBytes: AGENTMAIL_MAX_BODY_BYTES,
51+
maxBytes: WEBHOOK_MAX_BODY_BYTES,
5552
label: AGENTMAIL_BODY_LABEL,
5653
})
5754
return new TextDecoder().decode(buffer)
@@ -65,7 +62,7 @@ export const POST = withRouteHandler(async (req: Request) => {
6562
} catch (bodyError) {
6663
if (isPayloadSizeLimitError(bodyError)) {
6764
logger.warn('Rejected oversized AgentMail webhook body', {
68-
maxBytes: AGENTMAIL_MAX_BODY_BYTES,
65+
maxBytes: WEBHOOK_MAX_BODY_BYTES,
6966
observedBytes: bodyError.observedBytes,
7067
})
7168
return NextResponse.json({ error: 'Request body too large' }, { status: 413 })

apps/sim/lib/webhooks/constants.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { env } from '@/lib/core/config/env'
2+
3+
/**
4+
* Maximum size of a webhook request body read into memory. The webhook receivers
5+
* are public and unauthenticated, so the body must be bounded before it is
6+
* buffered to prevent a memory-exhaustion DoS. Provider payloads rarely exceed a
7+
* few MB; defaults to 10 MB and is overridable via `WEBHOOK_MAX_REQUEST_BYTES`.
8+
*
9+
* Shared by every public webhook receiver so the cap is a single source of truth.
10+
*/
11+
export const WEBHOOK_MAX_BODY_BYTES =
12+
Number.parseInt(env.WEBHOOK_MAX_REQUEST_BYTES, 10) || 10 * 1024 * 1024

apps/sim/lib/webhooks/processor.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { isOrganizationOnTeamOrEnterprisePlan } from '@/lib/billing/core/subscri
99
import { tryAdmit } from '@/lib/core/admission/gate'
1010
import { getInlineJobQueue, getJobQueue, shouldExecuteInline } from '@/lib/core/async-jobs'
1111
import type { AsyncExecutionCorrelation } from '@/lib/core/async-jobs/types'
12-
import { env } from '@/lib/core/config/env'
1312
import { isProd } from '@/lib/core/config/feature-flags'
1413
import {
1514
assertContentLengthWithinLimit,
@@ -18,6 +17,7 @@ import {
1817
} from '@/lib/core/utils/stream-limits'
1918
import { getEffectiveDecryptedEnv } from '@/lib/environment/utils'
2019
import { preprocessExecution } from '@/lib/execution/preprocessing'
20+
import { WEBHOOK_MAX_BODY_BYTES } from '@/lib/webhooks/constants'
2121
import {
2222
getPendingWebhookVerification,
2323
matchesPendingWebhookVerificationProbe,
@@ -77,15 +77,6 @@ async function verifyCredentialSetBilling(credentialSetId: string): Promise<{
7777
return { valid: true }
7878
}
7979

80-
/**
81-
* Maximum size of a webhook request body read into memory. The webhook receiver
82-
* is public and unauthenticated, so the body must be bounded before it is
83-
* buffered to prevent a memory-exhaustion DoS. Provider payloads rarely exceed a
84-
* few MB; defaults to 10 MB and is overridable via `WEBHOOK_MAX_REQUEST_BYTES`.
85-
*/
86-
export const WEBHOOK_MAX_BODY_BYTES =
87-
Number.parseInt(env.WEBHOOK_MAX_REQUEST_BYTES, 10) || 10 * 1024 * 1024
88-
8980
const WEBHOOK_BODY_LABEL = 'Webhook request body'
9081

9182
export async function parseWebhookBody(
@@ -104,7 +95,9 @@ export async function parseWebhookBody(
10495
})
10596
rawBody = new TextDecoder().decode(buffer)
10697
} else {
107-
rawBody = await request.clone().text()
98+
// A null body stream means the request carries no body, so the parsed
99+
// body is empty — no second clone needed.
100+
rawBody = ''
108101
}
109102

110103
if (!rawBody || rawBody.length === 0) {

0 commit comments

Comments
 (0)