balda 0.0.63 → 0.0.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.cts CHANGED
@@ -136,6 +136,17 @@ type StaticPluginOptions = {
136
136
  * @example "/assets" or "/public"
137
137
  */
138
138
  path: string;
139
+ /**
140
+ * Policy for dotfiles (files starting with `.`).
141
+ * - "ignore": return 404 (default)
142
+ * - "deny": return 403
143
+ * - "allow": serve normally
144
+ */
145
+ dotfiles?: "ignore" | "deny" | "allow";
146
+ /**
147
+ * Allow following symlinks. Disabled by default for security.
148
+ */
149
+ followSymlinks?: boolean;
139
150
  };
140
151
  type FileAllowedMimeType = "text/html" | "text/css" | "application/javascript" | "application/typescript" | "text/jsx" | "text/tsx" | "application/json" | "application/xml" | "application/yaml" | "text/csv" | "text/plain" | "text/markdown" | "image/png" | "image/jpeg" | "image/gif" | "image/svg+xml" | "image/x-icon" | "image/webp" | "image/avif" | "image/bmp" | "image/tiff" | "image/heic" | "image/heif" | "video/mp4" | "video/webm" | "video/x-msvideo" | "video/quicktime" | "video/x-matroska" | "video/x-ms-wmv" | "video/x-flv" | "video/x-m4v" | "video/mpeg" | "video/3gpp" | "audio/mpeg" | "audio/wav" | "audio/ogg" | "audio/flac" | "audio/aac" | "audio/mp4" | "audio/x-ms-wma" | "audio/opus" | "audio/midi" | "font/woff" | "font/woff2" | "font/ttf" | "font/otf" | "application/vnd.ms-fontobject" | "application/pdf" | "application/msword" | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | "application/vnd.ms-excel" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | "application/vnd.ms-powerpoint" | "application/vnd.openxmlformats-officedocument.presentationml.presentation" | "application/vnd.oasis.opendocument.text" | "application/vnd.oasis.opendocument.spreadsheet" | "application/vnd.oasis.opendocument.presentation" | "application/rtf" | "application/epub+zip" | "application/zip" | "application/x-tar" | "application/gzip" | "application/x-bzip2" | "application/x-xz" | "application/vnd.rar" | "application/x-7z-compressed" | "application/wasm" | "application/manifest+json" | "text/calendar" | "text/vcard" | "application/sql" | "application/x-sh" | "application/x-msdos-program" | "application/x-msdownload" | "application/octet-stream" | "application/x-iso9660-image" | "application/x-apple-diskimage" | "application/vnd.android.package-archive" | "application/java-archive" | "application/x-shockwave-flash";
141
152
 
@@ -182,6 +193,118 @@ type FilePluginOptions = {
182
193
  allowedMimeTypes?: (FileAllowedMimeType | (string & {}))[];
183
194
  };
184
195
 
196
+ type SessionStore = {
197
+ get: (sid: string) => Promise<Record<string, any> | undefined>;
198
+ set: (sid: string, value: Record<string, any>, ttlSeconds?: number) => Promise<void>;
199
+ destroy: (sid: string) => Promise<void>;
200
+ };
201
+ type SessionOptions = {
202
+ /** Cookie name used for session id */
203
+ name?: string;
204
+ /**
205
+ * Secret for signing the session cookie (sets `signed: true` on the session cookie).
206
+ * Requires `cookie({ sign: true, secret })` with the same secret.
207
+ */
208
+ secret?: string;
209
+ /** TTL seconds for session */
210
+ ttl?: number;
211
+ /** Custom store, default is in-memory */
212
+ store?: SessionStore;
213
+ /** Whether to set HttpOnly secure flags */
214
+ cookie?: {
215
+ path?: string;
216
+ httpOnly?: boolean;
217
+ secure?: boolean;
218
+ sameSite?: "Strict" | "Lax" | "None";
219
+ domain?: string;
220
+ };
221
+ };
222
+
223
+ /**
224
+ * Cookie options for setting cookies
225
+ */
226
+ type CookieOptions = {
227
+ /**
228
+ * Domain for the cookie.
229
+ * ⚠️ Must not contain CR, LF, semicolons, or other control chars.
230
+ */
231
+ domain?: string;
232
+ /**
233
+ * Path for the cookie.
234
+ * ⚠️ Must not contain CR, LF, or semicolons.
235
+ */
236
+ path?: string;
237
+ /**
238
+ * Expiration date for the cookie.
239
+ * ⚠️ Will throw if the Date is invalid (NaN getTime).
240
+ */
241
+ expires?: Date;
242
+ /**
243
+ * Max age in seconds for the cookie. Must be a non-negative integer.
244
+ * Falsy values (including 0) are only skipped if undefined;
245
+ * pass maxAge: 0 to immediately expire.
246
+ */
247
+ maxAge?: number;
248
+ /**
249
+ * Whether the cookie is secure (HTTPS only)
250
+ * @default true
251
+ *
252
+ * ⚠️ Must be `true` when `sameSite` is `"None"`.
253
+ */
254
+ secure?: boolean;
255
+ /**
256
+ * Whether the cookie is HTTP only (prevents JavaScript access)
257
+ * @default true
258
+ */
259
+ httpOnly?: boolean;
260
+ /**
261
+ * SameSite attribute for the cookie
262
+ *
263
+ * - "Strict": Most secure, cookie not sent on cross-site requests
264
+ * - "Lax": Balanced, cookie sent on top-level navigation
265
+ * - "None": Least secure, requires secure=true
266
+ *
267
+ * ⚠️ "None" requires `secure: true`; combination is rejected at runtime.
268
+ */
269
+ sameSite?: "Strict" | "Lax" | "None";
270
+ /**
271
+ * Whether this individual cookie should be signed.
272
+ * The middleware must have `sign: true` and a `secret` set for this to work.
273
+ * Overrides the global `sign` option for this cookie only.
274
+ */
275
+ signed?: boolean;
276
+ /**
277
+ * Priority for the cookie
278
+ */
279
+ priority?: "Low" | "Medium" | "High";
280
+ };
281
+ /**
282
+ * Options for the cookie middleware
283
+ */
284
+ type CookieMiddlewareOptions = {
285
+ /**
286
+ * Secret key(s) for signing cookies.
287
+ * - Provide a single string for static signing.
288
+ * - Provide an array for key rotation: signing uses `secret[0]`,
289
+ * verification accepts any entry in the array.
290
+ * Required when `sign` is enabled.
291
+ */
292
+ secret?: string | string[];
293
+ /**
294
+ * Default options applied to all cookies set via `res.cookie()`.
295
+ */
296
+ defaults?: CookieOptions;
297
+ /**
298
+ * Whether to enable cookie parsing (defaults to true)
299
+ */
300
+ parse?: boolean;
301
+ /**
302
+ * Whether to enable cookie signing by default for all cookies (defaults to false).
303
+ * Individual cookies can override this via `CookieOptions.signed`.
304
+ */
305
+ sign?: boolean;
306
+ };
307
+
185
308
  /**
186
309
  * The request object with type-safe path parameters.
187
310
  * This is the main object that is passed to the handler function.
@@ -349,6 +472,8 @@ declare class Request<Params extends Record<string, string> = Record<string, str
349
472
  file(fieldName: string): FormFile | null;
350
473
  get cookies(): Record<string, string>;
351
474
  set cookies(value: Record<string, string>);
475
+ get signedCookies(): Record<string, string>;
476
+ set signedCookies(value: Record<string, string>);
352
477
  /**
353
478
  * Returns cookies when the cookie middleware has populated them, otherwise undefined.
354
479
  * Useful for compatibility layers that should not throw when cookies are unavailable.
@@ -366,6 +491,36 @@ declare class Request<Params extends Record<string, string> = Record<string, str
366
491
  * @timeout middleware is required
367
492
  */
368
493
  timeout?: boolean;
494
+ /**
495
+ * Session dirty tracking - true if session was modified and needs to be saved.
496
+ * @internal
497
+ */
498
+ _sessionDirty: boolean;
499
+ /**
500
+ * Session ID for the current request.
501
+ * @internal
502
+ */
503
+ _sessionId?: string;
504
+ /**
505
+ * Session TTL in seconds.
506
+ * @internal
507
+ */
508
+ _sessionTtl?: number;
509
+ /**
510
+ * Session store instance.
511
+ * @internal
512
+ */
513
+ _sessionStore?: SessionStore;
514
+ /**
515
+ * Session cookie name.
516
+ * @internal
517
+ */
518
+ _sessionCookieName?: string;
519
+ /**
520
+ * Session cookie defaults.
521
+ * @internal
522
+ */
523
+ _sessionCookieDefaults?: CookieOptions;
369
524
  get session(): Record<string, any> | undefined;
370
525
  set session(value: Record<string, any> | undefined);
371
526
  /**
@@ -380,6 +535,7 @@ declare class Request<Params extends Record<string, string> = Record<string, str
380
535
  */
381
536
  private static readonly _throwSaveSession;
382
537
  private static readonly _throwDestroySession;
538
+ private static readonly _throwRegenerateSession;
383
539
  /**
384
540
  * Save the current session data.
385
541
  * @cookie middleware is required
@@ -394,6 +550,14 @@ declare class Request<Params extends Record<string, string> = Record<string, str
394
550
  * @throws Error if session middleware is not registered
395
551
  */
396
552
  destroySession: () => Promise<void>;
553
+ /**
554
+ * Regenerate the session: invalidates the old session ID and issues a new one.
555
+ * Call this after authentication to prevent session fixation attacks.
556
+ * @cookie middleware is required
557
+ * @session middleware is required
558
+ * @throws Error if session middleware is not registered
559
+ */
560
+ regenerateSession: () => Promise<void>;
397
561
  get ip(): string | undefined;
398
562
  set ip(value: string | undefined);
399
563
  /**
@@ -497,7 +661,7 @@ declare class Request<Params extends Record<string, string> = Record<string, str
497
661
  * Sets a lazy IP extractor for Bun runtime.
498
662
  * @internal
499
663
  */
500
- setBunIpExtractor(req: globalThis.Request, server: any): void;
664
+ setBunIpExtractor(_req: globalThis.Request, server: any): void;
501
665
  /**
502
666
  * Sets a lazy IP extractor for Deno runtime.
503
667
  * @internal
@@ -1103,81 +1267,6 @@ type OpenIdConnectOptions = {
1103
1267
  */
1104
1268
  declare const controller: (path?: string, swaggerOptions?: SwaggerRouteOptions) => (target: any) => void;
1105
1269
 
1106
- /**
1107
- * Cookie options for setting cookies
1108
- */
1109
- type CookieOptions = {
1110
- /**
1111
- * Domain for the cookie
1112
- */
1113
- domain?: string;
1114
- /**
1115
- * Path for the cookie
1116
- */
1117
- path?: string;
1118
- /**
1119
- * Expiration date for the cookie
1120
- */
1121
- expires?: Date;
1122
- /**
1123
- * Max age in seconds for the cookie
1124
- */
1125
- maxAge?: number;
1126
- /**
1127
- * Whether the cookie is secure (HTTPS only)
1128
- * @default true
1129
- *
1130
- * ⚠️ Should be `true` in production to prevent transmission over HTTP
1131
- */
1132
- secure?: boolean;
1133
- /**
1134
- * Whether the cookie is HTTP only (prevents JavaScript access)
1135
- * @default true
1136
- *
1137
- * ✅ Recommended: `true` to prevent XSS attacks
1138
- */
1139
- httpOnly?: boolean;
1140
- /**
1141
- * SameSite attribute for the cookie
1142
- *
1143
- * - "Strict": Most secure, cookie not sent on cross-site requests
1144
- * - "Lax": Balanced, cookie sent on top-level navigation
1145
- * - "None": Least secure, requires secure=true
1146
- *
1147
- * ✅ Recommended: "Strict" for auth cookies
1148
- */
1149
- sameSite?: "Strict" | "Lax" | "None";
1150
- /**
1151
- * Whether the cookie should be signed
1152
- */
1153
- signed?: boolean;
1154
- /**
1155
- * Priority for the cookie
1156
- */
1157
- priority?: "Low" | "Medium" | "High";
1158
- };
1159
- /**
1160
- * Options for the cookie middleware
1161
- */
1162
- type CookieMiddlewareOptions = {
1163
- /**
1164
- * Secret key for signing cookies (required if using signed cookies)
1165
- */
1166
- secret?: string;
1167
- /**
1168
- * Default options for all cookies set by this middleware
1169
- */
1170
- defaults?: CookieOptions;
1171
- /**
1172
- * Whether to enable cookie parsing (defaults to true)
1173
- */
1174
- parse?: boolean;
1175
- /**
1176
- * Whether to enable cookie signing (defaults to false)
1177
- */
1178
- sign?: boolean;
1179
- };
1180
-
1181
1270
  /**
1182
1271
  * The response object with per-status-code type-safe response bodies.
1183
1272
  * When response schemas are provided (e.g. via the `responses` route option), each shorthand
@@ -1200,6 +1289,12 @@ declare class Response$1<TResponseMap extends Record<number, any> = Record<numbe
1200
1289
  * The headers of the response
1201
1290
  */
1202
1291
  headers: Record<string, string>;
1292
+ /**
1293
+ * Accumulated Set-Cookie header values.
1294
+ * Stored separately so each cookie is emitted as its own Set-Cookie header.
1295
+ * @internal
1296
+ */
1297
+ cookieHeaders: string[];
1203
1298
  /**
1204
1299
  * The body of the response
1205
1300
  */
@@ -1211,7 +1306,9 @@ declare class Response$1<TResponseMap extends Record<number, any> = Record<numbe
1211
1306
  */
1212
1307
  setRouteResponseSchemas(schemas?: Record<number, RequestSchema>): void;
1213
1308
  /**
1214
- * Set a header for the response
1309
+ * Set a header for the response.
1310
+ * Set-Cookie values are accumulated as separate headers (RFC 6265 compliance).
1311
+ * Throws if key or value contains CR or LF characters (header injection prevention).
1215
1312
  */
1216
1313
  setHeader(key: string, value: string): this;
1217
1314
  /**
@@ -1227,6 +1324,8 @@ declare class Response$1<TResponseMap extends Record<number, any> = Record<numbe
1227
1324
  * Send a response with the given body without any content type or encoding (as is), status defaults to 200
1228
1325
  */
1229
1326
  raw(body: any): void;
1327
+ /** @internal — used by compression middleware to replace the body after async transformation */
1328
+ setBodyDirect(body: any): void;
1230
1329
  /**
1231
1330
  * Send a response with the given text, status defaults to 200
1232
1331
  */
@@ -1657,6 +1756,7 @@ declare class MockResponse<T = any> {
1657
1756
  statusCode(): number;
1658
1757
  headers(): Record<string, string>;
1659
1758
  cookies(): Record<string, string>;
1759
+ rawCookieHeaders(): string[];
1660
1760
  assertStatus(status: number): this;
1661
1761
  assertHeader(header: string, value: string): this;
1662
1762
  assertHeaderExists(header: string): this;
@@ -1695,6 +1795,12 @@ declare class NativeFs {
1695
1795
  isSymbolicLink: boolean;
1696
1796
  size: number;
1697
1797
  }>;
1798
+ lstat(path: string): Promise<{
1799
+ isDirectory: boolean;
1800
+ isFile: boolean;
1801
+ isSymbolicLink: boolean;
1802
+ size: number;
1803
+ }>;
1698
1804
  unlink(path: string): Promise<void>;
1699
1805
  streamFile(path: string): Promise<ReadableStream>;
1700
1806
  readdir(path: string): Promise<string[]>;
@@ -2004,9 +2110,6 @@ interface ValidationErrorHandlerOptions<T extends RequestSchema = RequestSchema>
2004
2110
  map?: (error: SerializedValidationError, req: Request) => ValidatedData<T> | Promise<ValidatedData<T>>;
2005
2111
  }
2006
2112
 
2007
- /**
2008
- * The server class that is used to create and manage the server
2009
- */
2010
2113
  declare class Server<H extends NodeHttpClient = NodeHttpClient> implements ServerInterface {
2011
2114
  #private;
2012
2115
  readonly _brand: "BaldaServer";
@@ -2220,7 +2323,7 @@ declare class MockServer {
2220
2323
  interface JsonOptions {
2221
2324
  /**
2222
2325
  * The maximum size of the JSON body in bytes.
2223
- * If the body is larger than this limit, the request will be rejected.
2326
+ * Enforced at the stream level not reliant on Content-Length header.
2224
2327
  * Default: 100kb
2225
2328
  */
2226
2329
  sizeLimit?: `${number}mb` | `${number}kb`;
@@ -2237,6 +2340,17 @@ interface JsonOptions {
2237
2340
  status?: number;
2238
2341
  message?: string;
2239
2342
  };
2343
+ /**
2344
+ * Maximum nesting depth of parsed JSON. Protects against stack-overflow via deeply-nested objects.
2345
+ * Default: 32
2346
+ */
2347
+ maxDepth?: number;
2348
+ /**
2349
+ * Maximum total number of object keys across all nesting levels.
2350
+ * Protects against hash-flooding / CPU DoS via huge key counts.
2351
+ * Default: 10000
2352
+ */
2353
+ maxKeys?: number;
2240
2354
  }
2241
2355
 
2242
2356
  /**
@@ -2290,34 +2404,48 @@ type CompressionOptions = {
2290
2404
  * Default: common compressible types (text, json, xml, javascript, etc.)
2291
2405
  */
2292
2406
  filter?: RegExp[];
2407
+ /**
2408
+ * Optional predicate to opt out of compression for specific requests/responses.
2409
+ * Return true to skip compression (e.g. for sensitive endpoints to mitigate BREACH).
2410
+ */
2411
+ skipFor?: (req: Request, res: Response$1) => boolean;
2293
2412
  };
2294
2413
 
2295
2414
  type CorsMethods = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "PATCH" | "HEAD";
2296
2415
  /**
2297
- * Options for CORS middleware, similar to Express.js
2416
+ * Options for CORS middleware.
2417
+ *
2418
+ * ⚠️ SECURITY NOTES:
2419
+ * - `origin` is required. The old insecure `'*'` default has been removed.
2420
+ * - `origin: '*'` combined with `credentials: true` throws at construction.
2421
+ * - Regex origins: ensure patterns are anchored (^ and $) to prevent suffix-match bypasses.
2422
+ * Prefer an explicit string allowlist over regex.
2298
2423
  */
2299
2424
  type CorsOptions = {
2300
2425
  /**
2301
2426
  * Configures the Access-Control-Allow-Origin CORS header.
2302
2427
  *
2303
- * ⚠️ WARNING: The default value '*' allows ALL origins, which is insecure for production.
2428
+ * - `'*'` allow all origins (only for truly public APIs, cannot be used with credentials)
2429
+ * - `'https://example.com'` — exact single origin
2430
+ * - `['https://a.com', 'https://b.com']` — explicit allowlist (recommended for production)
2431
+ * - `/^https:\/\/[a-z]+\.example\.com$/` — anchored regex (use with caution)
2304
2432
  *
2305
- * For production, explicitly set allowed origins:
2306
- * - Single origin: 'https://example.com'
2307
- * - Multiple origins: ['https://example.com', 'https://app.example.com']
2308
- * - Pattern matching: /^https:\/\/.*\.example\.com$/
2309
- *
2310
- * Defaults to '*' (allows all origins) - only suitable for development/public APIs.
2433
+ * ⚠️ Regex patterns must be fully anchored. An unanchored pattern like
2434
+ * `/example\.com/` matches `https://evil.com/?x=example.com`.
2311
2435
  */
2312
- origin?: string | RegExp | (string | RegExp)[];
2436
+ origin: string | RegExp | (string | RegExp)[];
2313
2437
  /**
2314
- * Configures the Access-Control-Allow-Methods CORS header. Defaults to 'GET,HEAD,PUT,PATCH,POST,DELETE'.
2438
+ * Configures the Access-Control-Allow-Methods CORS header.
2439
+ * Defaults to 'GET, HEAD, PUT, PATCH, POST, DELETE'.
2315
2440
  */
2316
2441
  methods?: CorsMethods[] | string;
2317
2442
  /**
2318
- * Configures the Access-Control-Allow-Headers CORS header. Defaults to allowing all requested headers.
2319
- * When omitted, Balda echoes `Access-Control-Request-Headers` on preflight requests.
2320
- * Set this explicitly to override that default behavior.
2443
+ * Configures the Access-Control-Allow-Headers CORS header.
2444
+ * Defaults to `['Content-Type', 'Accept', 'Authorization']`.
2445
+ *
2446
+ * ⚠️ The old behavior of reflecting `Access-Control-Request-Headers` verbatim
2447
+ * has been removed — that allowed clients to bless arbitrary headers.
2448
+ * Set this explicitly to the headers your API actually needs.
2321
2449
  */
2322
2450
  allowedHeaders?: string[] | string;
2323
2451
  /**
@@ -2326,10 +2454,13 @@ type CorsOptions = {
2326
2454
  exposedHeaders?: string[] | string;
2327
2455
  /**
2328
2456
  * Configures the Access-Control-Allow-Credentials CORS header. Defaults to false.
2457
+ *
2458
+ * ⚠️ Cannot be `true` when `origin` is `'*'`.
2329
2459
  */
2330
2460
  credentials?: boolean;
2331
2461
  /**
2332
2462
  * Configures the Access-Control-Max-Age CORS header. Defaults to undefined (not sent).
2463
+ * Recommended maximum: 600 seconds for sensitive endpoints.
2333
2464
  */
2334
2465
  maxAge?: number;
2335
2466
  /**
@@ -2337,9 +2468,15 @@ type CorsOptions = {
2337
2468
  */
2338
2469
  preflightContinue?: boolean;
2339
2470
  /**
2340
- * Provides a status code to use for successful OPTIONS requests, if preflightContinue is false. Defaults to 204.
2471
+ * Provides a status code to use for successful OPTIONS requests, if preflightContinue is false.
2472
+ * Defaults to 204.
2341
2473
  */
2342
2474
  optionsSuccessStatus?: number;
2475
+ /**
2476
+ * Allow the special `null` origin (sent by sandboxed iframes and file:// pages).
2477
+ * Defaults to false. Only enable if you explicitly need to support these contexts.
2478
+ */
2479
+ allowNullOrigin?: boolean;
2343
2480
  };
2344
2481
 
2345
2482
  type ExpressRouter = IRouter;
@@ -2359,12 +2496,36 @@ interface HelmetOptions {
2359
2496
  };
2360
2497
  contentTypeOptions?: boolean;
2361
2498
  ieNoOpen?: boolean;
2499
+ /**
2500
+ * Controls the legacy X-XSS-Protection header.
2501
+ * Modern guidance is to omit this header (set to false).
2502
+ * @default false
2503
+ * @deprecated Use Content-Security-Policy instead.
2504
+ */
2505
+ xssLegacyHeader?: boolean;
2506
+ /** @deprecated Use xssLegacyHeader instead. */
2362
2507
  xssFilter?: boolean;
2363
2508
  referrerPolicy?: false | string;
2364
2509
  crossOriginResourcePolicy?: false | string;
2365
2510
  crossOriginOpenerPolicy?: false | string;
2366
2511
  crossOriginEmbedderPolicy?: false | string;
2512
+ /**
2513
+ * Content-Security-Policy header value.
2514
+ * Set to `false` to disable.
2515
+ * @default "default-src 'self'"
2516
+ */
2367
2517
  contentSecurityPolicy?: false | string;
2518
+ /**
2519
+ * Permissions-Policy header value.
2520
+ * Set to `false` to disable.
2521
+ * @default "camera=(), microphone=(), geolocation=()"
2522
+ */
2523
+ permissionsPolicy?: false | string;
2524
+ /**
2525
+ * Emit `Origin-Agent-Cluster: ?1` header to opt in to origin-keyed agent clusters.
2526
+ * @default true
2527
+ */
2528
+ originAgentCluster?: boolean;
2368
2529
  }
2369
2530
 
2370
2531
  type LoggerOptions = Parameters<typeof pino$1>[0];
@@ -2419,6 +2580,12 @@ type MethodOverrideOptions = {
2419
2580
  * Default: 'X-HTTP-Method-Override'
2420
2581
  */
2421
2582
  header?: string;
2583
+ /**
2584
+ * Disable the cross-site Origin/Referer check.
2585
+ * ⚠️ Only set to true if you have an independent CSRF defense in place.
2586
+ * Default: false (check is enabled)
2587
+ */
2588
+ disableCsrfCheck?: boolean;
2422
2589
  };
2423
2590
 
2424
2591
  type StorageStrategy = "memory" | "custom";
@@ -2443,6 +2610,11 @@ type BaseRateLimiterOptions = {
2443
2610
  * @default "memory"
2444
2611
  */
2445
2612
  storageStrategy?: StorageStrategy;
2613
+ /**
2614
+ * When storage throws, return 429 (fail closed) instead of allowing the request (fail open).
2615
+ * @default false
2616
+ */
2617
+ failClosed?: boolean;
2446
2618
  };
2447
2619
  /**
2448
2620
  * Key Strategy
@@ -2481,9 +2653,26 @@ type MemoryStorageStrategy = {
2481
2653
  * @default 60000
2482
2654
  */
2483
2655
  windowMs?: number;
2656
+ /**
2657
+ * Maximum number of unique keys to track. When full, the oldest key is evicted.
2658
+ * Protects against memory exhaustion from attacker-controlled key cardinality.
2659
+ * @default 100000
2660
+ */
2661
+ maxKeys?: number;
2484
2662
  };
2485
2663
  /**
2486
- * Custom Storage Strategy
2664
+ * Atomic increment result returned by custom storage.
2665
+ */
2666
+ type IncrementResult = {
2667
+ /** Current request count for this key in the active window */
2668
+ count: number;
2669
+ /** Unix timestamp (ms) when the current window resets */
2670
+ resetAt: number;
2671
+ };
2672
+ /**
2673
+ * Custom Storage Strategy.
2674
+ * Must implement an atomic increment that initialises a new fixed window on first call
2675
+ * or after window expiry.
2487
2676
  */
2488
2677
  type CustomStorageStrategy = {
2489
2678
  /**
@@ -2491,13 +2680,11 @@ type CustomStorageStrategy = {
2491
2680
  */
2492
2681
  type: "custom";
2493
2682
  /**
2494
- * Set a value in the storage
2495
- */
2496
- set: (key: string, value: any) => Promise<void>;
2497
- /**
2498
- * Get a value from the storage
2683
+ * Atomically increment the counter for `key` within a `windowMs`-wide fixed window.
2684
+ * Should create a new window if none exists or the current one has expired.
2685
+ * Returns the updated count and the window's reset timestamp.
2499
2686
  */
2500
- get: (key: string) => Promise<any>;
2687
+ increment: (key: string, windowMs: number) => Promise<IncrementResult>;
2501
2688
  };
2502
2689
  type StorageOptions$1 = MemoryStorageStrategy | CustomStorageStrategy;
2503
2690
  /**
@@ -2505,30 +2692,6 @@ type StorageOptions$1 = MemoryStorageStrategy | CustomStorageStrategy;
2505
2692
  */
2506
2693
  type RateLimiterKeyOptions = IpRateLimiterOptions | CustomRateLimiterOptions;
2507
2694
 
2508
- type SessionStore = {
2509
- get: (sid: string) => Promise<Record<string, any> | undefined>;
2510
- set: (sid: string, value: Record<string, any>, ttlSeconds?: number) => Promise<void>;
2511
- destroy: (sid: string) => Promise<void>;
2512
- };
2513
- type SessionOptions = {
2514
- /** Cookie name used for session id */
2515
- name?: string;
2516
- /** Secret used to sign session id (optional, for future) */
2517
- secret?: string;
2518
- /** TTL seconds for session */
2519
- ttl?: number;
2520
- /** Custom store, default is in-memory */
2521
- store?: SessionStore;
2522
- /** Whether to set HttpOnly secure flags */
2523
- cookie?: {
2524
- path?: string;
2525
- httpOnly?: boolean;
2526
- secure?: boolean;
2527
- sameSite?: "Strict" | "Lax" | "None";
2528
- domain?: string;
2529
- };
2530
- };
2531
-
2532
2695
  /**
2533
2696
  * Swagger plugin that serves the swagger UI and JSON specification, by default the UI will be available at /docs and the JSON specification at /docs/json
2534
2697
  * @warning The json specification is always available at /${globalOptions.path}/json
@@ -2542,12 +2705,26 @@ type TimeoutOptions = {
2542
2705
  };
2543
2706
 
2544
2707
  type TrustProxyOptions = {
2545
- /** Enable using X-Forwarded-* headers to determine client IP */
2546
- trust?: boolean;
2547
- /** Header name to read the client IP chain from */
2708
+ /**
2709
+ * Allowlist of trusted proxy IPs (exact match or IPv4 CIDR notation).
2710
+ * The direct peer's socket IP must appear in this list before any
2711
+ * X-Forwarded-For header is consumed.
2712
+ *
2713
+ * @example ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '127.0.0.1']
2714
+ */
2715
+ trustedProxies: string[];
2716
+ /**
2717
+ * Number of trusted proxy hops in the X-Forwarded-For chain (counted from the right).
2718
+ * With hops = 1, the client IP is the last entry in the XFF list.
2719
+ * With hops = 2, the client IP is the second-to-last entry.
2720
+ * @default 1
2721
+ */
2722
+ hops?: number;
2723
+ /**
2724
+ * Header name to read the client IP chain from.
2725
+ * @default "x-forwarded-for"
2726
+ */
2548
2727
  header?: string;
2549
- /** Select which IP from the chain to use: 'first' (client) or 'last' (closest proxy) */
2550
- hop?: "first" | "last";
2551
2728
  };
2552
2729
 
2553
2730
  declare class PolicyManager<T extends Record<string, PolicyProvider>> {
@@ -3223,6 +3400,23 @@ declare const validate: {
3223
3400
  }): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
3224
3401
  };
3225
3402
 
3403
+ type VariantHandler<TInput, TOutput, TCtx = unknown> = (input: TInput, ctx?: TCtx) => TOutput | Promise<TOutput>;
3404
+ interface VariantConfig<TInput = unknown, TCtx = unknown> {
3405
+ handler: VariantHandler<TInput, unknown, TCtx>;
3406
+ schema?: RequestSchema;
3407
+ }
3408
+ interface SerializerBuilder<TInput, TDeclaredVariants extends string, TVariantMap extends Record<string, unknown>, TCtx = unknown> {
3409
+ defineVariant<const TName extends string, TSchema extends RequestSchema>(name: TName, schema: TSchema, handler: (input: TInput, ctx?: TCtx) => ValidatedData<TSchema> | Promise<ValidatedData<TSchema>>): SerializerBuilder<TInput, TDeclaredVariants | TName, TVariantMap & Record<TName, ValidatedData<TSchema>>, TCtx>;
3410
+ defineVariant<const TName extends string, TOutput>(name: TName, handler: (input: TInput, ctx?: TCtx) => TOutput | Promise<TOutput>): SerializerBuilder<TInput, TDeclaredVariants | TName, TVariantMap & Record<TName, TOutput>, TCtx>;
3411
+ useVariant<TName extends TDeclaredVariants>(name: TName, data: TInput, options?: {
3412
+ ctx?: TCtx;
3413
+ validate?: boolean;
3414
+ }): Promise<TVariantMap[TName]>;
3415
+ }
3416
+
3417
+ declare function serializer<TSchema extends RequestSchema, TCtx extends RequestSchema | undefined = undefined>(inputSchema: TSchema, ctxSchema?: TCtx): SerializerBuilder<ValidatedData<TSchema>, never, {}, TCtx extends RequestSchema ? ValidatedData<TCtx> : unknown>;
3418
+ declare function serializer<TInput = unknown, TCtx = unknown>(): SerializerBuilder<TInput, never, {}, TCtx>;
3419
+
3226
3420
  /**
3227
3421
  * Base class for cron jobs with logger instance
3228
3422
  * @example
@@ -4678,43 +4872,42 @@ declare abstract class BasePlugin {
4678
4872
  }
4679
4873
 
4680
4874
  /**
4681
- * Compression middleware for gzip compression of response bodies
4875
+ * Compression middleware for gzip compression of response bodies.
4876
+ *
4877
+ * Uses async gzip to avoid blocking the event loop on large payloads.
4878
+ * Always emits `Vary: Accept-Encoding` to prevent cache poisoning.
4879
+ *
4880
+ * BREACH/CRIME note: if an endpoint reflects user-controlled data in a compressed
4881
+ * response, use `skipFor` to opt that route out of compression.
4682
4882
  *
4683
4883
  * @param options Compression middleware options
4684
4884
  */
4685
4885
  declare const compression: (options?: CompressionOptions) => ServerRouteMiddleware;
4686
4886
 
4687
4887
  /**
4688
- * Cookie middleware for parsing and setting cookies, must be used in order to use the cookie methods on the request and response objects
4689
- *
4690
- * @param options Cookie middleware options
4888
+ * Cookie middleware for parsing and setting cookies.
4889
+ * Must be used before any handler that accesses `req.cookies` or `req.signedCookies`.
4691
4890
  */
4692
4891
  declare const cookie: (options?: CookieMiddlewareOptions) => TypedMiddleware<{
4693
4892
  cookies: Record<string, string>;
4893
+ signedCookies: Record<string, string>;
4694
4894
  }>;
4695
4895
 
4696
4896
  /**
4697
4897
  * CORS plugin
4698
4898
  *
4699
- * ⚠️ SECURITY WARNING: By default, this plugin allows ALL origins ('*').
4700
- * For production environments, explicitly configure allowed origins.
4701
- *
4702
- * @param options CORS options (all optional). Omitted options keep Balda's defaults,
4703
- * including default methods and echoing `Access-Control-Request-Headers` on preflight
4704
- * requests unless `allowedHeaders` is explicitly overridden.
4899
+ * ⚠️ SECURITY: `origin` is now required. The old wildcard default has been removed.
4900
+ * Using `origin: "*"` with `credentials: true` throws at construction.
4705
4901
  *
4706
4902
  * @example
4707
- * // Development (permissive)
4708
- * cors()
4903
+ * // Explicit allowlist (production)
4904
+ * cors({ origin: ['https://example.com', 'https://app.example.com'] })
4709
4905
  *
4710
4906
  * @example
4711
- * // Production (secure)
4712
- * cors({
4713
- * origin: ['https://example.com', 'https://app.example.com'],
4714
- * credentials: true
4715
- * })
4907
+ * // Public API (no credentials)
4908
+ * cors({ origin: '*' })
4716
4909
  */
4717
- declare const cors: (options?: CorsOptions) => ServerRouteMiddleware;
4910
+ declare const cors: (options: CorsOptions) => ServerRouteMiddleware;
4718
4911
 
4719
4912
  /**
4720
4913
  * Converts an Express middleware to a Balda middleware
@@ -4765,18 +4958,23 @@ declare const helmet: (options?: HelmetOptions) => ServerRouteMiddleware;
4765
4958
  declare const log: (options?: LogOptions) => ServerRouteMiddleware;
4766
4959
 
4767
4960
  /**
4768
- * Method override middleware for supporting HTTP verbs like PUT/DELETE in clients that don't support them
4961
+ * Method override middleware for supporting HTTP verbs like PUT/DELETE in clients that don't support them.
4962
+ *
4963
+ * ⚠️ CSRF risk: overriding to state-changing methods (PUT, PATCH, DELETE) via a browser form
4964
+ * can bypass CSRF protections. This middleware rejects cross-site requests by default
4965
+ * (based on Origin / Referer vs. Host). Disable with `disableCsrfCheck: true` only when
4966
+ * you have an independent CSRF defense layer.
4769
4967
  *
4770
4968
  * @param options Method override middleware options
4771
4969
  */
4772
4970
  declare const methodOverride: (options?: MethodOverrideOptions) => ServerRouteMiddleware;
4773
4971
 
4774
4972
  /**
4775
- * Rate limiter plugin
4776
- * Rate limiter is a plugin that limits the number of requests to a resource.
4777
- * It can be used to protect a resource from abuse.
4778
- * @param keyOptions Rate limiter key options, tells the middleware how to retrieve the key from the request in to discriminate what to rate limit (all optional, defaults to ip)
4779
- * @param storageOptions Rate limiter storage options, tells the middleware how to store the rate limit data (all optional, defaults to memory)
4973
+ * Rate limiter plugin.
4974
+ * Uses a fixed-window, atomic-increment counter keyed on IP or a custom value.
4975
+ *
4976
+ * @param keyOptions How to derive the rate-limit key from the request.
4977
+ * @param storageOptions Where to store counters (in-memory or custom atomic store).
4780
4978
  */
4781
4979
  declare const rateLimiter: (keyOptions?: RateLimiterKeyOptions, storageOptions?: StorageOptions$1) => ServerRouteMiddleware;
4782
4980
 
@@ -4794,6 +4992,7 @@ declare const session: (options?: SessionOptions) => TypedMiddleware<{
4794
4992
  session: Record<string, any>;
4795
4993
  saveSession: () => Promise<void>;
4796
4994
  destroySession: () => Promise<void>;
4995
+ regenerateSession: () => Promise<void>;
4797
4996
  }>;
4798
4997
 
4799
4998
  /**
@@ -4823,13 +5022,17 @@ declare const timeout: (options: TimeoutOptions) => TypedMiddleware<{
4823
5022
  }>;
4824
5023
 
4825
5024
  /**
4826
- * Trust proxy plugin middleware, used to trust the proxy headers to get the client ip
4827
- * @param options Trust proxy options (all optional)
4828
- * @param options.trust Whether to trust the proxy headers
4829
- * @param options.header The header name to read the client IP chain from
4830
- * @param options.hop The hop to use to get the client IP
5025
+ * Trust proxy middleware for consuming X-Forwarded-For headers safely.
5026
+ *
5027
+ * @example
5028
+ * // Single reverse proxy at 10.0.0.1
5029
+ * trustProxy({ trustedProxies: ['10.0.0.1'] })
5030
+ *
5031
+ * @example
5032
+ * // Two proxy hops; proxies in a private subnet
5033
+ * trustProxy({ trustedProxies: ['10.0.0.0/8'], hops: 2 })
4831
5034
  */
4832
- declare const trustProxy: (options?: TrustProxyOptions) => ServerRouteMiddleware;
5035
+ declare const trustProxy: (options: TrustProxyOptions) => ServerRouteMiddleware;
4833
5036
 
4834
5037
  /**
4835
5038
  * Middleware to parse the body of the request. GET, DELETE and OPTIONS requests are not parsed. Used internally by the server. If no parser is set, the body will be parsed as raw array buffer.
@@ -5029,4 +5232,4 @@ declare enum CacheStatus {
5029
5232
  */
5030
5233
  declare const router: ClientRouter;
5031
5234
 
5032
- export { type AsyncLocalStorageContextSetters, AzureBlobStorageProvider, BaseCron, BasePlugin, type BaseStorageProviderOptions, type BlobStorageProviderOptions, type BodyParserOptions, type BodylessMethodOptions, BullMQConfiguration, type BullMQConfigurationOptions, BullMQPubSub, CACHE_STATUS_HEADER, type CacheKeyIncludes, type CacheMiddlewareOptions, type CachePluginOptions, type CacheProvider, type CacheRedisOptions, type CacheRouteConfig, CacheService, type CacheServiceInterface, type CacheStats, CacheStatus, Command, type CommandOptions, CommandRegistry, type CompressionOptions, type CookieMiddlewareOptions, type CorsOptions, type CronSchedule, type CronScheduleParams, CronService, type CronUIOptions, CustomAdapter, type CustomQueueConfiguration, type CustomStorageProviderOptions, CustomTypedQueue, type CustomValidationError, DEFAULT_CACHE_OPTIONS, EdgeAdapter, EjsAdapter, type ExtractParams, GraphQL, type GraphQLContext, type GraphQLOptions, type GraphQLResolverFunction, type GraphQLResolverMap, type GraphQLResolverType, type GraphQLResolvers, type GraphQLSchemaInput, type GraphQLTypeDef, type GroupRouter, HandlebarsAdapter, type HelmetOptions, type HttpMethod, type HttpsOptions, type InferMiddlewareExtension, type InferMiddlewareExtensions, type InferResponseMap, type InferSchemaType, type InjectFunction, LocalStorageProvider, type LocalStorageProviderOptions, type LockBehavior, type LogOptions, type LoggerOptions, type MailOptions, MailOptionsBuilder, MailProvider, type MailProviderInterface, Mailer, type MailerInterface, type MailerOptions, type MailerProviderOptions, MemoryCacheProvider, MemoryPubSub, type MethodOverrideOptions, MockResponse, MockServer, type MockServerOptions, type MqttConnectionOptions, type MqttHandler, type MqttPublishOptions, MqttService, type MqttSubscribeOptions, type MqttSubscription, type MqttTopics, MustacheAdapter, type NextFunction, type NodeHttpClient, type NodeServer as NodeHttpServerClient, PGBossConfiguration, type PGBossConfigurationOptions, PGBossPubSub, type PolicyDecorator, type PolicyErrorHandlerOptions, PolicyManager, type PolicyProvider, type PolicyRouteConfig, type PublishTopic, QueueManager, QueueService, type RateLimiterKeyOptions, RedisCacheProvider, Request, type RequestSchema, Response$1 as Response, type ResponseBodyForStatus, type RuntimeServer, S3StorageProvider, type S3StorageProviderOptions, SQSConfiguration, type SQSConfigurationOptions, SQSPubSub, type CacheMetrics as SchemaCacheMetrics, type SerializeOptions, type SerializedValidationError, Server, type ServerConnectInput, type ServerErrorHandler, type ServerHook, type ServerInterface, type ServerListenCallback, type ServerOptions, type ServerPlugin, type ServerPluginConfig, type ServerRouteHandler, type ServerRouteMiddleware, type ServerTapOptions, type SessionOptions, type SignalEvent, type StandardMethodOptions, type StaticPluginOptions, Storage, type StorageInterface, type StorageOptions, type StorageProviderOptions, type TemplateMailOptions, type TimeoutOptions, type TrustProxyOptions, type TypedCacheKeyIncludes, type TypedCacheRouteConfig, type TypedHandler, type TypedMiddleware, TypedQueue, type TypedRouteMetadata, type ValidatedData, type ValidationErrorHandlerOptions, type ValidationOptions, arg, asyncLocalStorage, asyncStorage, bodyParser, bullmqQueue, cache, cacheMiddleware, clearAllCaches as clearAllSchemaCaches, commandRegistry, compression, controller, cookie, cors, createExpressAdapter, createPolicyDecorator, createQueue, cron, cronUIInstance, cronUi, Server as default, defineMiddleware, defineQueueConfiguration, del, expressHandler, expressMiddleware, flag, get, getCacheService, getCacheMetrics as getSchemaCacheMetrics, hash, helmet, initCacheService, log, logCacheMetrics as logSchemaCacheMetrics, logger, memoryQueue, methodOverride, middleware, mountExpressRouter, mqtt, patch, pgbossQueue, post, put, rateLimiter, resetCacheService, router, serialize, serveStatic, session, setCronGlobalErrorHandler, setMqttGlobalErrorHandler, sqsQueue, timeout as timeoutMw, trustProxy, validate };
5235
+ export { type AsyncLocalStorageContextSetters, AzureBlobStorageProvider, BaseCron, BasePlugin, type BaseStorageProviderOptions, type BlobStorageProviderOptions, type BodyParserOptions, type BodylessMethodOptions, BullMQConfiguration, type BullMQConfigurationOptions, BullMQPubSub, CACHE_STATUS_HEADER, type CacheKeyIncludes, type CacheMiddlewareOptions, type CachePluginOptions, type CacheProvider, type CacheRedisOptions, type CacheRouteConfig, CacheService, type CacheServiceInterface, type CacheStats, CacheStatus, Command, type CommandOptions, CommandRegistry, type CompressionOptions, type CookieMiddlewareOptions, type CorsOptions, type CronSchedule, type CronScheduleParams, CronService, type CronUIOptions, CustomAdapter, type CustomQueueConfiguration, type CustomStorageProviderOptions, CustomTypedQueue, type CustomValidationError, DEFAULT_CACHE_OPTIONS, EdgeAdapter, EjsAdapter, type ExtractParams, GraphQL, type GraphQLContext, type GraphQLOptions, type GraphQLResolverFunction, type GraphQLResolverMap, type GraphQLResolverType, type GraphQLResolvers, type GraphQLSchemaInput, type GraphQLTypeDef, type GroupRouter, HandlebarsAdapter, type HelmetOptions, type HttpMethod, type HttpsOptions, type InferMiddlewareExtension, type InferMiddlewareExtensions, type InferResponseMap, type InferSchemaType, type InjectFunction, LocalStorageProvider, type LocalStorageProviderOptions, type LockBehavior, type LogOptions, type LoggerOptions, type MailOptions, MailOptionsBuilder, MailProvider, type MailProviderInterface, Mailer, type MailerInterface, type MailerOptions, type MailerProviderOptions, MemoryCacheProvider, MemoryPubSub, type MethodOverrideOptions, MockResponse, MockServer, type MockServerOptions, type MqttConnectionOptions, type MqttHandler, type MqttPublishOptions, MqttService, type MqttSubscribeOptions, type MqttSubscription, type MqttTopics, MustacheAdapter, type NextFunction, type NodeHttpClient, type NodeServer as NodeHttpServerClient, PGBossConfiguration, type PGBossConfigurationOptions, PGBossPubSub, type PolicyDecorator, type PolicyErrorHandlerOptions, PolicyManager, type PolicyProvider, type PolicyRouteConfig, type PublishTopic, QueueManager, QueueService, type RateLimiterKeyOptions, RedisCacheProvider, Request, type RequestSchema, Response$1 as Response, type ResponseBodyForStatus, type RuntimeServer, S3StorageProvider, type S3StorageProviderOptions, SQSConfiguration, type SQSConfigurationOptions, SQSPubSub, type CacheMetrics as SchemaCacheMetrics, type SerializeOptions, type SerializedValidationError, type SerializerBuilder, Server, type ServerConnectInput, type ServerErrorHandler, type ServerHook, type ServerInterface, type ServerListenCallback, type ServerOptions, type ServerPlugin, type ServerPluginConfig, type ServerRouteHandler, type ServerRouteMiddleware, type ServerTapOptions, type SessionOptions, type SignalEvent, type StandardMethodOptions, type StaticPluginOptions, Storage, type StorageInterface, type StorageOptions, type StorageProviderOptions, type TemplateMailOptions, type TimeoutOptions, type TrustProxyOptions, type TypedCacheKeyIncludes, type TypedCacheRouteConfig, type TypedHandler, type TypedMiddleware, TypedQueue, type TypedRouteMetadata, type ValidatedData, type ValidationErrorHandlerOptions, type ValidationOptions, type VariantConfig, type VariantHandler, arg, asyncLocalStorage, asyncStorage, bodyParser, bullmqQueue, cache, cacheMiddleware, clearAllCaches as clearAllSchemaCaches, commandRegistry, compression, controller, cookie, cors, createExpressAdapter, createPolicyDecorator, createQueue, cron, cronUIInstance, cronUi, Server as default, defineMiddleware, defineQueueConfiguration, del, expressHandler, expressMiddleware, flag, get, getCacheService, getCacheMetrics as getSchemaCacheMetrics, hash, helmet, initCacheService, log, logCacheMetrics as logSchemaCacheMetrics, logger, memoryQueue, methodOverride, middleware, mountExpressRouter, mqtt, patch, pgbossQueue, post, put, rateLimiter, resetCacheService, router, serialize, serializer, serveStatic, session, setCronGlobalErrorHandler, setMqttGlobalErrorHandler, sqsQueue, timeout as timeoutMw, trustProxy, validate };