From 67bee695e65ebe9b4161f70152c7600ceadbf68e Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Sun, 12 May 2024 13:21:06 -1000 Subject: [PATCH] feat(api): :sparkles: Add safeguard when using formdata without a boundary --- index.ts | 2 ++ middlewares/boundary-check.ts | 18 ++++++++++++++++++ tests/api.test.ts | 25 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 middlewares/boundary-check.ts diff --git a/index.ts b/index.ts index 610477f3..4188cce5 100644 --- a/index.ts +++ b/index.ts @@ -7,6 +7,7 @@ import { LogLevel, LogManager, type MultiLogManager } from "log-manager"; import { setupDatabase } from "~drizzle/db"; import { agentBans } from "~middlewares/agent-bans"; import { bait } from "~middlewares/bait"; +import { boundaryCheck } from "~middlewares/boundary-check"; import { ipBans } from "~middlewares/ip-bans"; import { logger } from "~middlewares/logger"; import { Note } from "~packages/database-interface/note"; @@ -113,6 +114,7 @@ app.use(ipBans); app.use(agentBans); app.use(bait); app.use(logger); +app.use(boundaryCheck); // Inject own filesystem router for (const [route, path] of Object.entries(routes)) { diff --git a/middlewares/boundary-check.ts b/middlewares/boundary-check.ts new file mode 100644 index 00000000..83eb4b5f --- /dev/null +++ b/middlewares/boundary-check.ts @@ -0,0 +1,18 @@ +import { errorResponse } from "@response"; +import { createMiddleware } from "hono/factory"; + +export const boundaryCheck = createMiddleware(async (context, next) => { + // Checks that FormData boundary is present + const contentType = context.req.header("content-type"); + + if (contentType?.includes("multipart/form-data")) { + if (!contentType.includes("boundary")) { + return errorResponse( + "You are sending a request with a multipart/form-data content type but without a boundary. Please include a boundary in the Content-Type header. For more information, visit https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data", + 400, + ); + } + } + + await next(); +}); diff --git a/tests/api.test.ts b/tests/api.test.ts index 6ec0af04..afef150a 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -60,4 +60,29 @@ describe("API Tests", () => { await db.delete(Emojis).where(eq(Emojis.shortcode, "test")); }); }); + + test("Try sending FormData without a boundary", async () => { + const formData = new FormData(); + formData.append("test", "test"); + + const response = await sendTestRequest( + new Request( + wrapRelativeUrl(`${base_url}/api/v1/custom_emojis`, base_url), + { + method: "GET", + headers: { + Authorization: `Bearer ${tokens[0].accessToken}`, + "Content-Type": "multipart/form-data", + }, + body: formData, + }, + ), + ); + + expect(response.status).toBe(400); + const data = await response.json(); + + expect(data.error).toBeString(); + expect(data.error).toContain("https://stackoverflow.com"); + }); });