fix: enforce API request limits
Add a single platform-owned request-body policy (hono/body-limit) with small, named limits for Better Auth and Core's grant-mutation endpoint, and a new required maxBodyBytes field on the executable module route contract so every module route makes a conscious no-body-or-bounded-body choice, capped at a platform ceiling. Every oversized request gets one sanitized 413 contract and never reaches business logic, authorization, or a handler. Also configure finite Node header/request/keep-alive timeouts, including the non-obvious connectionsCheckingInterval needed for them to take effect promptly, so a slow-header or slow-body connection cannot hold a socket indefinitely. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
cafd513019
commit
d3c6bd73d3
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
isModuleHttpMethod,
|
||||
MODULE_HTTP_METHODS,
|
||||
MODULE_ROUTE_MAX_BODY_BYTES_CEILING,
|
||||
type ModuleRouteDefinitionInput,
|
||||
moduleRoutePathSchema,
|
||||
moduleRouteShapeKey,
|
||||
@@ -22,6 +23,7 @@ function validDefinition(
|
||||
method: "GET",
|
||||
path: "/events/:eventId",
|
||||
permission: "calendar.events.read",
|
||||
maxBodyBytes: 0,
|
||||
handler: noopHandler,
|
||||
...overrides,
|
||||
};
|
||||
@@ -153,6 +155,7 @@ describe("safeParseModuleRouteDefinition", () => {
|
||||
method: "GET",
|
||||
path: "/events/:eventId",
|
||||
permission: "calendar.events.read",
|
||||
maxBodyBytes: 0,
|
||||
handler: noopHandler,
|
||||
});
|
||||
expect(Object.isFrozen(result.data)).toBe(true);
|
||||
@@ -216,6 +219,44 @@ describe("safeParseModuleRouteDefinition", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts a maxBodyBytes of exactly zero (no body accepted) and exactly the platform ceiling", () => {
|
||||
expect(safeParseModuleRouteDefinition(validDefinition({ maxBodyBytes: 0 })).success).toBe(true);
|
||||
const atCeiling = safeParseModuleRouteDefinition(
|
||||
validDefinition({ maxBodyBytes: MODULE_ROUTE_MAX_BODY_BYTES_CEILING }),
|
||||
);
|
||||
expect(atCeiling.success).toBe(true);
|
||||
if (atCeiling.success) {
|
||||
expect(atCeiling.data.maxBodyBytes).toBe(MODULE_ROUTE_MAX_BODY_BYTES_CEILING);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a negative, non-integer, non-finite, or over-ceiling maxBodyBytes", () => {
|
||||
for (const maxBodyBytes of [
|
||||
-1,
|
||||
1.5,
|
||||
Number.NaN,
|
||||
Number.POSITIVE_INFINITY,
|
||||
Number.NEGATIVE_INFINITY,
|
||||
MODULE_ROUTE_MAX_BODY_BYTES_CEILING + 1,
|
||||
]) {
|
||||
expect(safeParseModuleRouteDefinition(validDefinition({ maxBodyBytes }))).toEqual({
|
||||
success: false,
|
||||
code: "MODULE_ROUTE_MAX_BODY_BYTES_INVALID",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a non-numeric or missing maxBodyBytes", () => {
|
||||
expect(
|
||||
safeParseModuleRouteDefinition({ ...validDefinition(), maxBodyBytes: "0" }),
|
||||
).toMatchObject({ success: false, code: "MODULE_ROUTE_MAX_BODY_BYTES_INVALID" });
|
||||
const { maxBodyBytes: _maxBodyBytes, ...withoutMaxBodyBytes } = validDefinition();
|
||||
expect(safeParseModuleRouteDefinition(withoutMaxBodyBytes)).toEqual({
|
||||
success: false,
|
||||
code: "MODULE_ROUTE_DEFINITION_INVALID",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not retain the caller's mutable definition object: mutating it afterward cannot change the parsed result", () => {
|
||||
const source = { ...validDefinition() };
|
||||
const result = safeParseModuleRouteDefinition(source);
|
||||
|
||||
Reference in New Issue
Block a user