Skip to content

Commit b7f6acd

Browse files
committed
fix(files): report an oversized document body as a size rejection
Capping the body read meant an oversized document threw PayloadSizeLimitError, which nothing caught, so the caller got a generic 500 — hiding the very limit message the cap was added to produce. It now returns the same 400 as the bundle check, naming the export limit.
1 parent f16e140 commit b7f6acd

2 files changed

Lines changed: 33 additions & 6 deletions

File tree

apps/sim/app/api/files/export/[id]/route.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { createMockRequest } from '@sim/testing'
55
import JSZip from 'jszip'
66
import { beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { PayloadSizeLimitError } from '@/lib/core/utils/stream-limits'
78

89
const {
910
mockCheckAuth,
@@ -136,6 +137,19 @@ describe('markdown export bundling', () => {
136137
expect(bodyCall?.[0].maxBytes).toBe(250 * MB)
137138
})
138139

140+
it('reports an oversized body as a size rejection, not a server error', async () => {
141+
mockExtractEmbeddedImageIds.mockReturnValue([])
142+
mockDownloadFile.mockRejectedValue(
143+
new PayloadSizeLimitError({ label: 'storage file download', maxBytes: 1 })
144+
)
145+
146+
const response = await GET(request(), context)
147+
148+
// The cap exists to produce a clear limit message; a 500 would hide it.
149+
expect(response.status).toBe(400)
150+
expect((await response.json()).error).toContain('export limit')
151+
})
152+
139153
it('caps each asset download rather than trusting its declared size', async () => {
140154
mockExtractEmbeddedImageIds.mockReturnValue(['a'])
141155

apps/sim/app/api/files/export/[id]/route.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { parseRequest } from '@/lib/api/server'
1010
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
1111
import { extractEmbeddedImageIds } from '@/lib/copilot/tools/server/files/embedded-image-refs'
1212
import { MATERIALIZE_CONCURRENCY, mapWithConcurrency } from '@/lib/core/utils/concurrency'
13+
import { isPayloadSizeLimitError } from '@/lib/core/utils/stream-limits'
1314
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1415
import { captureServerEvent } from '@/lib/posthog/server'
1516
import type { StorageContext } from '@/lib/uploads/config'
@@ -130,12 +131,24 @@ export const GET = withRouteHandler(
130131

131132
// Capped like everything else in the bundle: the document body is usually the
132133
// largest single entry, so leaving it unbounded left the export limit unenforced
133-
// against the one item most able to exceed it.
134-
const mdBuffer = await downloadFile({
135-
key: record.key,
136-
context: record.context as StorageContext,
137-
maxBytes: MAX_EXPORT_TOTAL_BYTES,
138-
})
134+
// against the one item most able to exceed it. A body that alone exceeds the limit
135+
// is a size rejection, so it reports as one rather than as a server error.
136+
let mdBuffer: Buffer
137+
try {
138+
mdBuffer = await downloadFile({
139+
key: record.key,
140+
context: record.context as StorageContext,
141+
maxBytes: MAX_EXPORT_TOTAL_BYTES,
142+
})
143+
} catch (error) {
144+
if (!isPayloadSizeLimitError(error)) throw error
145+
return NextResponse.json(
146+
{
147+
error: `This document exceeds the ${formatFileSize(MAX_EXPORT_TOTAL_BYTES)} export limit.`,
148+
},
149+
{ status: 400 }
150+
)
151+
}
139152
let mdContent = mdBuffer.toString('utf-8')
140153

141154
const imageIds = extractEmbeddedImageIds(mdContent)

0 commit comments

Comments
 (0)