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:
ekklesia
2026-07-26 12:29:24 -05:00
co-authored by Claude Sonnet 5
parent cafd513019
commit d3c6bd73d3
24 changed files with 1419 additions and 96 deletions
+14 -2
View File
@@ -11,6 +11,7 @@ import {
requirePermission,
requireTrustedOrigin,
} from "./authorization-middleware.js";
import { platformBodyLimit } from "./body-limits.js";
const MODULE_ROUTE_PREFIX = "/api/v1/modules";
const STATE_CHANGING_METHODS: ReadonlySet<string> = new Set(["DELETE", "PATCH", "POST", "PUT"]);
@@ -187,6 +188,10 @@ export function mountModuleHttpRoutes(
}
const requireRoutePermission = requirePermission(middlewareOptions, route.permission);
// The route's own conscious body contract (`route.maxBodyBytes` — 0 means no body accepted at
// all) is enforced before permission evaluation ever runs, so an oversized request never
// reaches authorization work, let alone the handler or the module database.
const routeBodyLimit = platformBodyLimit(route.maxBodyBytes);
const path = moduleRoutePath(route.moduleId, route.path);
const finalHandler = async (c: Context): Promise<Response> => {
const context: ModuleRouteHandlerContext = Object.freeze({
@@ -212,9 +217,16 @@ export function mountModuleHttpRoutes(
// array — each branch below is therefore a distinct, fixed-arity call rather than one call
// spreading a variable-length handlers array.
if (STATE_CHANGING_METHODS.has(route.method)) {
app.on(route.method, path, requireOrigin, requireRoutePermission, finalHandler);
app.on(
route.method,
path,
requireOrigin,
routeBodyLimit,
requireRoutePermission,
finalHandler,
);
} else {
app.on(route.method, path, requireRoutePermission, finalHandler);
app.on(route.method, path, routeBodyLimit, requireRoutePermission, finalHandler);
}
}
}