Skip to content

Commit cbc76d7

Browse files
committed
fix(files): route the raw serve branch through the same response ceiling
The `raw=1` branch returned before the check, so the ceiling held for the branches that transform bytes but not for the one that returns them unchanged. Those bytes are already bounded by the read that produced them, so this changes no behavior — it makes the guarantee hold for everything the resolver returns rather than for every branch someone remembered to cover.
1 parent edfcb0d commit cbc76d7

3 files changed

Lines changed: 15 additions & 26 deletions

File tree

‎apps/sim/app/api/files/public/[token]/content/route.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export const GET = withRouteHandler(
129129
// replace it with bytes fetched or produced separately — a compiled artifact, a
130130
// page with its images inlined, a transcoded derivative. This is an anonymous
131131
// route, so the bytes it actually returns are what has to fit.
132-
assertKnownSizeWithinLimit(buffer.length, MAX_BUFFERED_TRANSFER_BYTES, 'served file render')
132+
assertKnownSizeWithinLimit(buffer.length, MAX_BUFFERED_TRANSFER_BYTES, 'served file response')
133133

134134
logger.info('Public shared file served', { token, key: file.key, size: buffer.length })
135135

‎apps/sim/app/api/files/serve/[...path]/route.ts‎

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -94,30 +94,16 @@ async function resolveServableBytes(params: {
9494
fileType?: string
9595
signal: AbortSignal | undefined
9696
}): Promise<{ buffer: Buffer; contentType: string }> {
97-
const {
98-
buffer,
99-
filename,
100-
storageKey,
101-
workspaceId,
102-
options,
103-
ownerKey,
104-
filePrincipal,
105-
fileType,
106-
signal,
107-
} = params
108-
if (options.raw) return { buffer, contentType: getContentType(filename) }
109-
return withinTransferCeiling(await resolveTransformedBytes(params))
110-
}
111-
112-
/** Rejects a resolved response whose bytes outgrew what one request may hold resident. */
113-
function withinTransferCeiling(resolved: { buffer: Buffer; contentType: string }): {
114-
buffer: Buffer
115-
contentType: string
116-
} {
97+
// `raw` is the stored source, already bounded by the read that produced it, but it
98+
// goes through the same check so the ceiling holds for everything this returns
99+
// rather than for every branch someone remembered to cover.
100+
const resolved = params.options.raw
101+
? { buffer: params.buffer, contentType: getContentType(params.filename) }
102+
: await resolveTransformedBytes(params)
117103
assertKnownSizeWithinLimit(
118104
resolved.buffer.length,
119105
MAX_BUFFERED_TRANSFER_BYTES,
120-
'served file render'
106+
'served file response'
121107
)
122108
return resolved
123109
}

‎apps/sim/lib/file-parsers/yaml-limits.test.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@ describe('measureYamlExpansion', () => {
3636
const shared = [1, 2, 3, 4, 5]
3737
const aliased = { a: shared, b: shared, c: shared }
3838

39-
const measured = measureYamlExpansion(aliased, limits({ maxNodes: 20 }))
40-
expect(measured).toEqual({ within: true, depth: 2 })
41-
// 18 nodes if every reach is charged; 8 if the shared array were counted once.
42-
expect(measureYamlExpansion(aliased, limits({ maxNodes: 12 })).within).toBe(false)
39+
// 19 nodes when every reach is charged (root + 3 refs + 3x5 elements); 9 if the
40+
// shared array were counted once, which is what makes an alias bomb invisible.
41+
expect(measureYamlExpansion(aliased, limits({ maxNodes: 19 }))).toEqual({
42+
within: true,
43+
depth: 2,
44+
})
45+
expect(measureYamlExpansion(aliased, limits({ maxNodes: 18 })).within).toBe(false)
4346
})
4447

4548
it('terminates on a self-referential anchor instead of recursing forever', () => {

0 commit comments

Comments
 (0)