Skip to content

Commit 3e22b65

Browse files
committed
fix(api): limit upload body size before parsing multipart
Hono buffers the whole multipart body before the asset size check in uploadAsset runs, so an oversized upload could exhaust the web process memory. Add a bodyLimit middleware before the form validator on the asset and singlefile upload routes.
1 parent 2d7cc4b commit 3e22b65

3 files changed

Lines changed: 13 additions & 2 deletions

File tree

‎packages/api/routes/assets.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { authMiddleware } from "../middlewares/auth";
1010
import { createRateLimitMiddleware } from "../middlewares/rateLimit";
1111
import { rejectMutationInReadOnlyMode } from "../middlewares/readOnlyMode";
1212
import { serveAsset } from "../utils/assets";
13-
import { uploadAsset } from "../utils/upload";
13+
import { uploadAsset, uploadBodyLimit } from "../utils/upload";
1414

1515
const app = new Hono()
1616
.use(authMiddleware)
@@ -23,6 +23,7 @@ const app = new Hono()
2323
windowMs: 60 * 1000,
2424
maxRequests: 30,
2525
}),
26+
uploadBodyLimit,
2627
zValidator(
2728
"form",
2829
z

‎packages/api/routes/bookmarks.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
zIncludeContentSearchParamsSchema,
2828
zStringBool,
2929
} from "../utils/types";
30-
import { uploadAsset } from "../utils/upload";
30+
import { uploadAsset, uploadBodyLimit } from "../utils/upload";
3131

3232
const app = new Hono()
3333
.use(authMiddleware)
@@ -138,6 +138,7 @@ const app = new Hono()
138138
.default("skip"),
139139
}),
140140
),
141+
uploadBodyLimit,
141142
zValidator(
142143
"form",
143144
z.object({

‎packages/api/utils/upload.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as path from "path";
44
import { Readable } from "stream";
55
import { pipeline } from "stream/promises";
66
import { fileTypeFromBlob, supportedMimeTypes } from "file-type";
7+
import { bodyLimit } from "hono/body-limit";
78

89
import { assets, AssetTypes } from "@karakeep/db/schema";
910
import {
@@ -18,6 +19,14 @@ import { AuthedContext } from "@karakeep/trpc";
1819

1920
const MAX_UPLOAD_SIZE_BYTES = serverConfig.maxAssetSizeMb * 1024 * 1024;
2021

22+
// Rejects oversized uploads before the multipart body gets buffered in memory.
23+
// The extra 1MiB leaves room for the multipart framing and other form fields,
24+
// the exact file size is still enforced in uploadAsset.
25+
export const uploadBodyLimit = bodyLimit({
26+
maxSize: MAX_UPLOAD_SIZE_BYTES + 1024 * 1024,
27+
onError: (c) => c.json({ error: "Asset is too big" }, 413),
28+
});
29+
2130
// Helper to convert Web Stream to Node Stream (requires Node >= 16.5 / 14.18)
2231
export function webStreamToNode(
2332
webStream: ReadableStream<Uint8Array>,

0 commit comments

Comments
 (0)