Originally created by: Ayush7614
On matched routes that declare a request body schema, DaloyJS reads and parses the full request body inside buildContext() before route/global beforeHandle hooks run (including bearerAuth(), rateLimit(), WAF, etc.).
Attackers can force expensive body I/O and JSON parsing on protected endpoints even when auth would reject the request immediately afterward.
This is not an auth bypass — the handler never runs. It is a resource-exhaustion / ordering issue.
@daloyjs/core@1.0.0-beta.4 (local monorepo main).1.0.0-beta.4app.request() in-process)Route with request.body schema + bearerAuth({ validate: () => false }):
| Request | Status | Interpretation |
|---|---|---|
| POST ~400KB valid JSON + invalid bearer | 403 | Body accepted/parsed; auth rejected after |
| POST malformed JSON + invalid bearer | 400 | JSON parse fails before auth (403 never reached) |
Dispatch order in src/app.ts:
buildContext() (includes readBody + safeJsonParse when body schema present, ~4410–4428)allHooks.beforeHandle() (bearerAuth, rateLimit, …)If requestDecompression is enabled as global onRequest, decompression also runs before routing (~442–468 in request-decompression.ts).
Cheap guards (beforeHandle: auth, rate limit, IP fence) should run before reading/parsing large request bodies, so unauthenticated clients cannot burn CPU/bandwidth on body work.
import { App, bearerAuth } from "@daloyjs/core";
import { z } from "zod";
const Body = z.object({ data: z.string() });
const app = new App({ env: "test", bodyLimitBytes: 512 * 1024 }).route({
method: "POST",
path: "/secret",
auth: { scheme: "bearer" },
hooks: bearerAuth({ validate: () => false }),
request: { body: Body },
responses: { 401: { description: "unauthorized" } },
handler: async () => ({ status: 200 as const, body: { ok: true } }),
});
// Malformed JSON → 400 (body phase) before auth
const bad = await app.request("/secret", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer x" },
body: "{not-json",
});
console.log("malformed:", bad.status); // 400
// Large valid JSON → auth rejection, but body already read/parsed
const huge = "A".repeat(400_000);
const big = await app.request("/secret", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer x" },
body: JSON.stringify({ data: huge }),
});
console.log("large + bad token:", big.status); // 403
Defer readBody / schema validation until after beforeHandle passes (or split a “cheap” pre-body phase for auth + rate limit). Keep unhappy-path tests proving body limits and prototype-pollution guards still reject bad payloads.
Medium — resource exhaustion on authenticated routes, not auth bypass.
Originally posted by: Ayush7614
cc: @devlinduldulao