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
@@ -199,6 +199,8 @@ export default defineModuleRuntime({
|
||||
method: "GET",
|
||||
path: "/events/:eventId",
|
||||
permission: "calendar.events.read",
|
||||
// This route accepts no request body. See the body-size contract below.
|
||||
maxBodyBytes: 0,
|
||||
async handler(request, context) {
|
||||
return Response.json({
|
||||
eventId: context.params.eventId,
|
||||
@@ -210,11 +212,23 @@ export default defineModuleRuntime({
|
||||
});
|
||||
```
|
||||
|
||||
A route definition has exactly four fields — `method`, `path`, `permission`, `handler` — and nothing
|
||||
else; an unknown field (a baseline, a mount prefix, CORS/origin policy, middleware, audit metadata,
|
||||
authentication behavior, or another module's namespace) fails registration outright rather than
|
||||
being ignored. `method` is one of `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, always uppercase; there is
|
||||
no `OPTIONS` (the platform owns CORS preflight) and no wildcard/all-method registration.
|
||||
A route definition has exactly five fields — `method`, `path`, `permission`, `maxBodyBytes`,
|
||||
`handler` — and nothing else; an unknown field (a baseline, a mount prefix, CORS/origin policy,
|
||||
middleware, audit metadata, authentication behavior, or another module's namespace) fails
|
||||
registration outright rather than being ignored. `method` is one of `GET`, `POST`, `PUT`, `PATCH`,
|
||||
`DELETE`, always uppercase; there is no `OPTIONS` (the platform owns CORS preflight) and no
|
||||
wildcard/all-method registration.
|
||||
|
||||
`maxBodyBytes` is a conscious, required request-body contract, not an optional or defaulted field:
|
||||
`0` means the route accepts no body at all, and a non-empty declared (`Content-Length`) or streamed
|
||||
body is rejected before the handler ever runs; any other value is the exact maximum number of bytes
|
||||
accepted. It must be a non-negative integer no greater than the platform ceiling,
|
||||
`MODULE_ROUTE_MAX_BODY_BYTES_CEILING` (`@ekklesia/contracts`, currently 1 MiB) — a negative,
|
||||
non-integer, non-finite, or over-ceiling value fails registration with
|
||||
`MODULE_ROUTE_MAX_BODY_BYTES_INVALID`. The platform enforces this limit itself, ahead of permission
|
||||
evaluation and the handler, using the identical sanitized `413 {"code":"REQUEST_BODY_TOO_LARGE"}`
|
||||
contract every other body-consuming route in the platform uses — see
|
||||
[API](../development/api.md#request-body-limits).
|
||||
|
||||
`path` is local to the module, never the path a caller actually sends. The platform alone owns the
|
||||
production mount prefix, `/api/v1/modules/<module-id>`: `"/"` is the module's own root, `"/events"`
|
||||
@@ -370,6 +384,7 @@ export default defineModuleRuntime({
|
||||
method: "GET",
|
||||
path: "/events",
|
||||
permission: "calendar.events.read",
|
||||
maxBodyBytes: 0,
|
||||
async handler(_request, context) {
|
||||
const rows = await context.services.database
|
||||
.select({ id: events.id, title: events.title })
|
||||
|
||||
Reference in New Issue
Block a user