@arcis/node 1.4.4 → 1.5.0

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.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -6
  3. package/dist/astro/index.js +6141 -0
  4. package/dist/astro/index.js.map +1 -0
  5. package/dist/astro/index.mjs +6136 -0
  6. package/dist/astro/index.mjs.map +1 -0
  7. package/dist/bun/index.js +6195 -0
  8. package/dist/bun/index.js.map +1 -0
  9. package/dist/bun/index.mjs +6189 -0
  10. package/dist/bun/index.mjs.map +1 -0
  11. package/dist/core/constants.d.ts +3 -2
  12. package/dist/core/constants.d.ts.map +1 -1
  13. package/dist/core/index.js +4 -3
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/core/index.mjs +4 -3
  16. package/dist/core/index.mjs.map +1 -1
  17. package/dist/core/types.d.ts +32 -0
  18. package/dist/core/types.d.ts.map +1 -1
  19. package/dist/fastify/index.js +6160 -0
  20. package/dist/fastify/index.js.map +1 -0
  21. package/dist/fastify/index.mjs +6155 -0
  22. package/dist/fastify/index.mjs.map +1 -0
  23. package/dist/guards.d.ts +156 -0
  24. package/dist/guards.d.ts.map +1 -0
  25. package/dist/hono/index.js +6159 -0
  26. package/dist/hono/index.js.map +1 -0
  27. package/dist/hono/index.mjs +6154 -0
  28. package/dist/hono/index.mjs.map +1 -0
  29. package/dist/index.d.ts +23 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +7126 -178
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +7088 -179
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/koa/index.js +6158 -0
  36. package/dist/koa/index.js.map +1 -0
  37. package/dist/koa/index.mjs +6153 -0
  38. package/dist/koa/index.mjs.map +1 -0
  39. package/dist/logging/index.js.map +1 -1
  40. package/dist/logging/index.mjs.map +1 -1
  41. package/dist/logging/redactor.d.ts.map +1 -1
  42. package/dist/middleware/astro.d.ts +64 -0
  43. package/dist/middleware/astro.d.ts.map +1 -0
  44. package/dist/middleware/bot-detection.d.ts.map +1 -1
  45. package/dist/middleware/bun.d.ts +75 -0
  46. package/dist/middleware/bun.d.ts.map +1 -0
  47. package/dist/middleware/csrf.d.ts.map +1 -1
  48. package/dist/middleware/error-handler.d.ts.map +1 -1
  49. package/dist/middleware/fastify.d.ts +89 -0
  50. package/dist/middleware/fastify.d.ts.map +1 -0
  51. package/dist/middleware/graphql.d.ts +35 -0
  52. package/dist/middleware/graphql.d.ts.map +1 -0
  53. package/dist/middleware/hono.d.ts +63 -0
  54. package/dist/middleware/hono.d.ts.map +1 -0
  55. package/dist/middleware/index.d.ts +12 -0
  56. package/dist/middleware/index.d.ts.map +1 -1
  57. package/dist/middleware/index.js +6469 -119
  58. package/dist/middleware/index.js.map +1 -1
  59. package/dist/middleware/index.mjs +6459 -120
  60. package/dist/middleware/index.mjs.map +1 -1
  61. package/dist/middleware/koa.d.ts +84 -0
  62. package/dist/middleware/koa.d.ts.map +1 -0
  63. package/dist/middleware/main.d.ts +0 -30
  64. package/dist/middleware/main.d.ts.map +1 -1
  65. package/dist/middleware/mass-assign.d.ts +81 -0
  66. package/dist/middleware/mass-assign.d.ts.map +1 -0
  67. package/dist/middleware/method-allowlist.d.ts +66 -0
  68. package/dist/middleware/method-allowlist.d.ts.map +1 -0
  69. package/dist/middleware/nestjs.d.ts +62 -0
  70. package/dist/middleware/nestjs.d.ts.map +1 -0
  71. package/dist/middleware/nextjs.d.ts +102 -0
  72. package/dist/middleware/nextjs.d.ts.map +1 -0
  73. package/dist/middleware/nuxt.d.ts +61 -0
  74. package/dist/middleware/nuxt.d.ts.map +1 -0
  75. package/dist/middleware/overload.d.ts +92 -0
  76. package/dist/middleware/overload.d.ts.map +1 -0
  77. package/dist/middleware/protect.d.ts +91 -0
  78. package/dist/middleware/protect.d.ts.map +1 -0
  79. package/dist/middleware/rate-limit-sliding.d.ts.map +1 -1
  80. package/dist/middleware/rate-limit-token.d.ts.map +1 -1
  81. package/dist/middleware/rate-limit.d.ts.map +1 -1
  82. package/dist/middleware/response-splitting.d.ts +83 -0
  83. package/dist/middleware/response-splitting.d.ts.map +1 -0
  84. package/dist/middleware/sveltekit.d.ts +68 -0
  85. package/dist/middleware/sveltekit.d.ts.map +1 -0
  86. package/dist/middleware/token-budget.d.ts +75 -0
  87. package/dist/middleware/token-budget.d.ts.map +1 -0
  88. package/dist/nestjs/index.js +1724 -0
  89. package/dist/nestjs/index.js.map +1 -0
  90. package/dist/nestjs/index.mjs +1717 -0
  91. package/dist/nestjs/index.mjs.map +1 -0
  92. package/dist/nextjs/index.js +6184 -0
  93. package/dist/nextjs/index.js.map +1 -0
  94. package/dist/nextjs/index.mjs +6178 -0
  95. package/dist/nextjs/index.mjs.map +1 -0
  96. package/dist/nuxt/index.js +6141 -0
  97. package/dist/nuxt/index.js.map +1 -0
  98. package/dist/nuxt/index.mjs +6136 -0
  99. package/dist/nuxt/index.mjs.map +1 -0
  100. package/dist/sanitizers/encode.d.ts.map +1 -1
  101. package/dist/sanitizers/graphql.d.ts +72 -0
  102. package/dist/sanitizers/graphql.d.ts.map +1 -0
  103. package/dist/sanitizers/headers.d.ts +18 -0
  104. package/dist/sanitizers/headers.d.ts.map +1 -1
  105. package/dist/sanitizers/index.d.ts +4 -1
  106. package/dist/sanitizers/index.d.ts.map +1 -1
  107. package/dist/sanitizers/index.js +140 -66
  108. package/dist/sanitizers/index.js.map +1 -1
  109. package/dist/sanitizers/index.mjs +135 -67
  110. package/dist/sanitizers/index.mjs.map +1 -1
  111. package/dist/sanitizers/prompt-injection.d.ts +62 -0
  112. package/dist/sanitizers/prompt-injection.d.ts.map +1 -0
  113. package/dist/sanitizers/sanitize.d.ts +1 -1
  114. package/dist/sanitizers/sanitize.d.ts.map +1 -1
  115. package/dist/sanitizers/xpath.d.ts +37 -0
  116. package/dist/sanitizers/xpath.d.ts.map +1 -0
  117. package/dist/stores/index.js +4 -4
  118. package/dist/stores/index.js.map +1 -1
  119. package/dist/stores/index.mjs +4 -4
  120. package/dist/stores/index.mjs.map +1 -1
  121. package/dist/stores/redis.d.ts +7 -1
  122. package/dist/stores/redis.d.ts.map +1 -1
  123. package/dist/sveltekit/index.js +6142 -0
  124. package/dist/sveltekit/index.js.map +1 -0
  125. package/dist/sveltekit/index.mjs +6137 -0
  126. package/dist/sveltekit/index.mjs.map +1 -0
  127. package/dist/validation/index.d.ts +2 -0
  128. package/dist/validation/index.d.ts.map +1 -1
  129. package/dist/validation/index.js +137 -12
  130. package/dist/validation/index.js.map +1 -1
  131. package/dist/validation/index.mjs +116 -13
  132. package/dist/validation/index.mjs.map +1 -1
  133. package/dist/validation/redirect.d.ts.map +1 -1
  134. package/dist/validation/schema.d.ts.map +1 -1
  135. package/dist/validation/url-async.d.ts +137 -0
  136. package/dist/validation/url-async.d.ts.map +1 -0
  137. package/package.json +52 -7
  138. package/scripts/postinstall.cjs +26 -0
  139. package/dist/cli/arcis.d.ts +0 -23
  140. package/dist/cli/arcis.d.ts.map +0 -1
  141. package/dist/cli/arcis.js +0 -312
  142. package/dist/cli/arcis.js.map +0 -1
  143. package/dist/cli/arcis.mjs +0 -309
  144. package/dist/cli/arcis.mjs.map +0 -1
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @module @arcis/node/middleware/protect
3
+ *
4
+ * Composite protection helpers (issue #52). Pre-configured middleware
5
+ * stacks for the three endpoint shapes that show up in every app:
6
+ * login, signup, generic API. Each helper composes EXISTING middleware
7
+ * with sensible defaults; no new security logic lives here.
8
+ *
9
+ * Express supports passing an array of middleware to a route — the
10
+ * elements get unrolled in declaration order — so each helper returns
11
+ * a `RequestHandler[]` that drops directly into `app.post(...)`:
12
+ *
13
+ * ```ts
14
+ * import { protectLogin, protectSignup, protectApi } from '@arcis/node';
15
+ *
16
+ * app.post('/login', protectLogin(), loginHandler);
17
+ * app.post('/signup', protectSignup(), signupHandler);
18
+ * app.use ('/api', protectApi());
19
+ * ```
20
+ *
21
+ * Defaults (issue #52 spec):
22
+ *
23
+ * | Helper | rate-limit | bot | csrf | cors | sanitize | email |
24
+ * |---------------|------------|-----|------|------|----------|-------|
25
+ * | protectLogin | 5/min | yes | yes | - | yes | - |
26
+ * | protectSignup | 3/min | yes | - | - | yes | yes |
27
+ * | protectApi | 100/min | - | - | yes | yes | - |
28
+ *
29
+ * Each option is overridable. Pass `{ rateLimit: false }` to disable a
30
+ * specific layer; pass an options object to forward to the underlying
31
+ * factory.
32
+ */
33
+ import type { RequestHandler } from 'express';
34
+ import { type BotProtectionOptions } from './bot-detection';
35
+ import { type CsrfOptions } from './csrf';
36
+ import type { CorsOptions } from './cors';
37
+ import { type SignupProtectionOptions } from './signup-protection';
38
+ import type { RateLimitOptions, SanitizeOptions } from '../core/types';
39
+ /**
40
+ * Per-layer override knob: pass `false` to disable, an options object
41
+ * to merge into the layer's defaults, or omit to accept the helper's
42
+ * baked-in default.
43
+ */
44
+ type LayerOverride<T> = false | T | undefined;
45
+ export interface ProtectLoginOptions {
46
+ rateLimit?: LayerOverride<RateLimitOptions>;
47
+ bot?: LayerOverride<BotProtectionOptions>;
48
+ csrf?: LayerOverride<CsrfOptions>;
49
+ sanitize?: LayerOverride<SanitizeOptions>;
50
+ }
51
+ export interface ProtectSignupOptions {
52
+ rateLimit?: LayerOverride<RateLimitOptions>;
53
+ bot?: LayerOverride<BotProtectionOptions>;
54
+ sanitize?: LayerOverride<SanitizeOptions>;
55
+ /** signupProtection options (email-style validation, disposable-mail block, etc.). */
56
+ signup?: LayerOverride<SignupProtectionOptions>;
57
+ }
58
+ export interface ProtectApiOptions {
59
+ rateLimit?: LayerOverride<RateLimitOptions>;
60
+ /** CORS is required to take an Origin/Methods config — no default origin. */
61
+ cors?: LayerOverride<CorsOptions>;
62
+ sanitize?: LayerOverride<SanitizeOptions>;
63
+ }
64
+ /**
65
+ * Login endpoints get the strictest defaults: 5 req/min/IP, deny
66
+ * AUTOMATED bots, CSRF token check, and input sanitization. Designed
67
+ * for `app.post('/login', protectLogin(), handler)`.
68
+ */
69
+ export declare function protectLogin(options?: ProtectLoginOptions): RequestHandler[];
70
+ /**
71
+ * Signup endpoints: 3 req/min/IP, deny AUTOMATED bots, sanitize input,
72
+ * and run signup-specific validation (email shape + disposable-domain
73
+ * check via `signupProtection`). No CSRF here because most signup
74
+ * forms are first-touch, no prior session to anchor a token to.
75
+ */
76
+ export declare function protectSignup(options?: ProtectSignupOptions): RequestHandler[];
77
+ /**
78
+ * Generic API endpoints: 100 req/min/IP, CORS, input sanitization. No
79
+ * bot detection by default because legitimate API consumers (curl,
80
+ * fetch, server-to-server) are often classified AUTOMATED — opt-in
81
+ * by passing a `bot` override... wait, protectApi doesn't expose bot.
82
+ * That's deliberate per the issue spec table. Users who want bot
83
+ * detection on API endpoints compose `botProtection()` directly.
84
+ *
85
+ * CORS is the one layer with no usable default — every app's allow
86
+ * list is different. Pass `cors: { origin: '...' }` or `cors: false`
87
+ * to skip it explicitly.
88
+ */
89
+ export declare function protectApi(options?: ProtectApiOptions): RequestHandler[];
90
+ export {};
91
+ //# sourceMappingURL=protect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protect.d.ts","sourceRoot":"","sources":["../../src/middleware/protect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAErF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEvE;;;;GAIG;AACH,KAAK,aAAa,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC;AAE9C,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAC1C,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAC1C,QAAQ,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC1C,sFAAsF;IACtF,MAAM,CAAC,EAAE,aAAa,CAAC,uBAAuB,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,6EAA6E;IAC7E,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CAC3C;AAaD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,cAAc,EAAE,CAoBhF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,cAAc,EAAE,CAoBlF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,EAAE,CAgB5E"}
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit-sliding.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit-sliding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAA0B,cAAc,EAAE,MAAM,SAAS,CAAC;AAI/E,MAAM,WAAW,oBAAoB;IACnC,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,0DAA0D;IAC1D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;CAClC;AAOD,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC7D,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,oBAAyB,GAAG,uBAAuB,CAiGtG"}
1
+ {"version":3,"file":"rate-limit-sliding.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit-sliding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAA0B,cAAc,EAAE,MAAM,SAAS,CAAC;AAI/E,MAAM,WAAW,oBAAoB;IACnC,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,0DAA0D;IAC1D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;CAClC;AAOD,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC7D,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,oBAAyB,GAAG,uBAAuB,CAwGtG"}
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit-token.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAA0B,cAAc,EAAE,MAAM,SAAS,CAAC;AAG/E,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,0DAA0D;IAC1D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;CAClC;AAOD,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,kBAAuB,GAAG,qBAAqB,CA2FhG"}
1
+ {"version":3,"file":"rate-limit-token.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAA0B,cAAc,EAAE,MAAM,SAAS,CAAC;AAG/E,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,0DAA0D;IAC1D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;CAClC;AAOD,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,kBAAuB,GAAG,qBAAqB,CA4FhG"}
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAkB,MAAM,eAAe,CAAC;AAO7F;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,gBAAqB,GAAG,qBAAqB,CAuIvF;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,0BAAoB,CAAC"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAkB,MAAM,eAAe,CAAC;AAO7F;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,gBAAqB,GAAG,qBAAqB,CAwIvF;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,0BAAoB,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @module @arcis/node/middleware/response-splitting
3
+ *
4
+ * HTTP response splitting prevention (sdk-vectors.md tier 1 #27).
5
+ *
6
+ * Response splitting is the *output* counterpart to header injection: app
7
+ * code passes user input into `res.setHeader`, `res.writeHead`, or
8
+ * `res.appendHeader` (Node 17+) without stripping CR/LF, and an attacker
9
+ * uses the embedded newline to break out of the header block and forge a
10
+ * second response. Most often weaponised against `Location:` after a
11
+ * redirect that reflects user input (`/redirect?to=...`).
12
+ *
13
+ * `sanitizeHeaderValue` already covers the byte-level fix on the way in;
14
+ * this middleware wraps the response object so every header that leaves
15
+ * the app gets sanitised on the way out — even when the app forgets.
16
+ *
17
+ * ```ts
18
+ * import { responseSplittingGuard } from '@arcis/node/middleware/response-splitting';
19
+ *
20
+ * app.use(responseSplittingGuard());
21
+ *
22
+ * // Later — even this passes through clean:
23
+ * app.get('/r', (req, res) => res.redirect(req.query.to as string));
24
+ * ```
25
+ *
26
+ * Pair with `validateRedirect` for full coverage: this middleware blocks
27
+ * the response-splitting payload, `validateRedirect` blocks the
28
+ * open-redirect payload.
29
+ */
30
+ import type { RequestHandler } from 'express';
31
+ import { detectHeaderInjection, sanitizeHeaderValue } from '../sanitizers/headers';
32
+ export interface ResponseSplittingGuardOptions {
33
+ /**
34
+ * What to do when an outgoing header value contains CR / LF / NUL.
35
+ *
36
+ * - `'strip'` (default) — silently sanitise the value before it reaches
37
+ * the wire. Preserves availability; existing routes don't break.
38
+ * - `'reject'` — throw a `ResponseSplittingError`. Use in apps that
39
+ * would rather fail-closed than emit a partial response.
40
+ *
41
+ * Both modes invoke `onDetect` if provided.
42
+ */
43
+ mode?: 'strip' | 'reject';
44
+ /**
45
+ * Per-detection callback. Fires before strip/reject. Useful for
46
+ * logging or alerting when an attempted split slips through into the
47
+ * response builder.
48
+ */
49
+ onDetect?: (header: string, originalValue: string) => void;
50
+ }
51
+ /**
52
+ * Thrown by `responseSplittingGuard({ mode: 'reject' })` when an
53
+ * outgoing header value contains CR / LF / NUL. The header name is in
54
+ * `header`; the originally attempted value is in `value` so it can be
55
+ * logged or surfaced in an error handler.
56
+ */
57
+ export declare class ResponseSplittingError extends Error {
58
+ readonly header: string;
59
+ readonly value: string;
60
+ constructor(header: string, value: string);
61
+ }
62
+ /**
63
+ * Re-export under the response-splitting name. Same byte pattern as
64
+ * header injection (CR / LF / NUL) — different threat model: input
65
+ * boundary vs output boundary.
66
+ */
67
+ export declare const detectResponseSplitting: typeof detectHeaderInjection;
68
+ /**
69
+ * Re-export under the response-splitting name. Strips CR / LF / NUL.
70
+ */
71
+ export declare const sanitizeResponseHeader: typeof sanitizeHeaderValue;
72
+ /**
73
+ * Build the response-splitting guard middleware. Wraps `res.setHeader`,
74
+ * `res.writeHead`, and `res.appendHeader` (when present) on each
75
+ * incoming request so every header that leaves the app gets the same
76
+ * CRLF / NUL treatment regardless of which code path emitted it.
77
+ *
78
+ * Wrapping happens per-request (not on the prototype) so multiple
79
+ * mounts with different options don't trample each other.
80
+ */
81
+ export declare function responseSplittingGuard(options?: ResponseSplittingGuardOptions): RequestHandler;
82
+ export default responseSplittingGuard;
83
+ //# sourceMappingURL=response-splitting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-splitting.d.ts","sourceRoot":"","sources":["../../src/middleware/response-splitting.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAY,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEnF,MAAM,WAAW,6BAA6B;IAC5C;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAE1B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5D;AAED;;;;;GAKG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAM1C;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,8BAAwB,CAAC;AAE7D;;GAEG;AACH,eAAO,MAAM,sBAAsB,4BAAsB,CAAC;AAiB1D;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,6BAAkC,GAC1C,cAAc,CAoHhB;AAED,eAAe,sBAAsB,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @module @arcis/node/sveltekit
3
+ *
4
+ * SvelteKit adapter for Arcis. Returns a `Handle` factory you can drop into
5
+ * `src/hooks.server.ts`:
6
+ *
7
+ * ```ts
8
+ * import { arcisHandle } from '@arcis/node/sveltekit';
9
+ * export const handle = arcisHandle({ rateLimit: { max: 100 }, bot: true });
10
+ * ```
11
+ *
12
+ * Or compose with other handles via SvelteKit's own `sequence` helper:
13
+ *
14
+ * ```ts
15
+ * import { sequence } from '@sveltejs/kit/hooks';
16
+ * import { arcisHandle } from '@arcis/node/sveltekit';
17
+ * export const handle = sequence(arcisHandle(), authHandle, loggingHandle);
18
+ * ```
19
+ *
20
+ * SvelteKit uses Web Fetch `Request`/`Response` objects, not Express
21
+ * `req`/`res`, so this adapter implements the Arcis pipeline natively against
22
+ * the Fetch API rather than wrapping `arcis()`. There is no runtime dependency
23
+ * on `@sveltejs/kit` — its types are imported only for compile-time checks.
24
+ */
25
+ import type { HeaderOptions, RateLimitOptions } from '../core/types';
26
+ import { type BotProtectionOptions } from './bot-detection';
27
+ interface SvelteKitCookies {
28
+ get(name: string): string | undefined;
29
+ set(name: string, value: string, opts: {
30
+ path: string;
31
+ [k: string]: unknown;
32
+ }): void;
33
+ delete(name: string, opts: {
34
+ path: string;
35
+ }): void;
36
+ }
37
+ export interface SvelteKitRequestEvent {
38
+ request: Request;
39
+ url: URL;
40
+ cookies: SvelteKitCookies;
41
+ getClientAddress(): string;
42
+ }
43
+ export type SvelteKitResolve = (event: SvelteKitRequestEvent, opts?: unknown) => Promise<Response> | Response;
44
+ export type SvelteKitHandle = (input: {
45
+ event: SvelteKitRequestEvent;
46
+ resolve: SvelteKitResolve;
47
+ }) => Promise<Response>;
48
+ export interface ArcisHandleOptions {
49
+ /** Security headers configuration. Default: enabled. Pass `false` to disable. */
50
+ headers?: boolean | HeaderOptions;
51
+ /** Rate limiter configuration. Default: 100 req/60s in-memory. Pass `false` to disable. */
52
+ rateLimit?: boolean | RateLimitOptions;
53
+ /**
54
+ * Bot protection. Default: disabled (opt-in to avoid surprising behavior on
55
+ * legitimate crawlers). Pass `true` for sensible defaults or an options
56
+ * object for full control.
57
+ */
58
+ bot?: boolean | BotProtectionOptions;
59
+ }
60
+ /**
61
+ * Build a SvelteKit `Handle` that applies Arcis protections in this order on
62
+ * each request: rate limit (returns 429 if exceeded), bot detection (returns
63
+ * 403 if the bot is in the deny list), then runs downstream `resolve(event)`,
64
+ * then mutates the resulting `Response`'s headers with security defaults.
65
+ */
66
+ export declare function arcisHandle(options?: ArcisHandleOptions): SvelteKitHandle;
67
+ export default arcisHandle;
68
+ //# sourceMappingURL=sveltekit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sveltekit.d.ts","sourceRoot":"","sources":["../../src/middleware/sveltekit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,KAAK,EACV,aAAa,EAEb,gBAAgB,EACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,KAAK,oBAAoB,EAE1B,MAAM,iBAAiB,CAAC;AAMzB,UAAU,gBAAgB;IACxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACtC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACrF,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,EAAE,gBAAgB,CAAC;IAC1B,gBAAgB,IAAI,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,gBAAgB,GAAG,CAC7B,KAAK,EAAE,qBAAqB,EAC5B,IAAI,CAAC,EAAE,OAAO,KACX,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;AAElC,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE;IACpC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,OAAO,EAAE,gBAAgB,CAAC;CAC3B,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAIxB,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IAClC,2FAA2F;IAC3F,SAAS,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,GAAG,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;CACtC;AAgID;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,eAAe,CAqE7E;AAED,eAAe,WAAW,CAAC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @module @arcis/node/middleware/token-budget
3
+ *
4
+ * Token-budget protection middleware. Caps per-key token spend over a
5
+ * sliding window — meant for routes that proxy LLM calls, where a tight
6
+ * 100-req/min rate limit isn't enough because a single 50KB prompt costs
7
+ * the same as 1000 small requests.
8
+ *
9
+ * @example
10
+ * import express from 'express';
11
+ * import { tokenBudget } from '@arcis/node';
12
+ *
13
+ * const guard = tokenBudget({
14
+ * maxTokens: 100_000, // 100K tokens per window
15
+ * windowMs: 60 * 60 * 1000, // 1-hour window
16
+ * maxRequestTokens: 5_000, // reject any single request over this
17
+ * keyGenerator: (req) => req.user?.id ?? req.ip,
18
+ * });
19
+ *
20
+ * app.post('/chat', guard, chatHandler);
21
+ *
22
+ * The default token estimator is `Math.ceil(bytesOf(body+query) / 4)` —
23
+ * close to OpenAI's "1 token ≈ 4 English characters" rule. Override via
24
+ * `estimateTokens` for accurate counting (tiktoken, etc.).
25
+ */
26
+ import type { Request, RequestHandler } from 'express';
27
+ export interface TokenBudgetOptions {
28
+ /** Max tokens a single key can spend in one window. Default: 100,000. */
29
+ maxTokens?: number;
30
+ /** Window length in milliseconds. Default: 60 * 60 * 1000 (1 hour). */
31
+ windowMs?: number;
32
+ /**
33
+ * Max tokens a single request may consume. Requests over this size are
34
+ * rejected with 413 BEFORE counting against the window budget. Default:
35
+ * unset (no per-request cap).
36
+ */
37
+ maxRequestTokens?: number;
38
+ /**
39
+ * Function that returns the budget key for a request. Default: client IP
40
+ * (req.ip), falling back to `unknown` when unresolvable.
41
+ */
42
+ keyGenerator?: (req: Request) => string;
43
+ /**
44
+ * Function that estimates the number of tokens a request will consume.
45
+ * Default: `Math.ceil((req.body + req.query stringified bytes) / 4)`,
46
+ * which approximates OpenAI's "1 token ≈ 4 characters" rule. Override
47
+ * with tiktoken/accurate counting when you need precision.
48
+ */
49
+ estimateTokens?: (req: Request) => number;
50
+ /** Status code for budget-exceeded responses. Default: 429. */
51
+ statusCode?: number;
52
+ /** Status code for oversize-request rejections. Default: 413. */
53
+ statusCodeOversize?: number;
54
+ /** Error message when budget is exceeded. */
55
+ message?: string;
56
+ /** Error message when a single request exceeds maxRequestTokens. */
57
+ messageOversize?: string;
58
+ /** Skip budget enforcement for certain requests. */
59
+ skip?: (req: Request) => boolean;
60
+ }
61
+ export interface TokenBudgetMiddleware extends RequestHandler {
62
+ /** Release internal cleanup resources. */
63
+ close: () => void;
64
+ /** Inspect current usage for a key (read-only; for tests/telemetry). */
65
+ inspect: (key: string) => {
66
+ used: number;
67
+ resetTime: number;
68
+ } | null;
69
+ }
70
+ /**
71
+ * Build a token-budget middleware. See module-level JSDoc for usage.
72
+ */
73
+ export declare function tokenBudget(options?: TokenBudgetOptions): TokenBudgetMiddleware;
74
+ export default tokenBudget;
75
+ //# sourceMappingURL=token-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-budget.d.ts","sourceRoot":"","sources":["../../src/middleware/token-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IAC1C,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oDAAoD;IACpD,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,0CAA0C;IAC1C,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,wEAAwE;IACxE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACtE;AAwCD;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,qBAAqB,CAkGnF;AAED,eAAe,WAAW,CAAC"}