@vivero/stoma 0.1.0-rc.10

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 (254) hide show
  1. package/CHANGELOG.md +196 -0
  2. package/LICENSE +21 -0
  3. package/README.md +325 -0
  4. package/dist/adapters/bun.d.ts +9 -0
  5. package/dist/adapters/bun.js +8 -0
  6. package/dist/adapters/bun.js.map +1 -0
  7. package/dist/adapters/cloudflare.d.ts +49 -0
  8. package/dist/adapters/cloudflare.js +85 -0
  9. package/dist/adapters/cloudflare.js.map +1 -0
  10. package/dist/adapters/deno.d.ts +9 -0
  11. package/dist/adapters/deno.js +8 -0
  12. package/dist/adapters/deno.js.map +1 -0
  13. package/dist/adapters/durable-object.d.ts +63 -0
  14. package/dist/adapters/durable-object.js +46 -0
  15. package/dist/adapters/durable-object.js.map +1 -0
  16. package/dist/adapters/index.d.ts +13 -0
  17. package/dist/adapters/index.js +53 -0
  18. package/dist/adapters/index.js.map +1 -0
  19. package/dist/adapters/memory.d.ts +9 -0
  20. package/dist/adapters/memory.js +14 -0
  21. package/dist/adapters/memory.js.map +1 -0
  22. package/dist/adapters/node.d.ts +9 -0
  23. package/dist/adapters/node.js +8 -0
  24. package/dist/adapters/node.js.map +1 -0
  25. package/dist/adapters/postgres.d.ts +109 -0
  26. package/dist/adapters/postgres.js +242 -0
  27. package/dist/adapters/postgres.js.map +1 -0
  28. package/dist/adapters/redis.d.ts +116 -0
  29. package/dist/adapters/redis.js +194 -0
  30. package/dist/adapters/redis.js.map +1 -0
  31. package/dist/adapters/testing.d.ts +32 -0
  32. package/dist/adapters/testing.js +33 -0
  33. package/dist/adapters/testing.js.map +1 -0
  34. package/dist/adapters/types.d.ts +4 -0
  35. package/dist/adapters/types.js +1 -0
  36. package/dist/adapters/types.js.map +1 -0
  37. package/dist/config/index.d.ts +11 -0
  38. package/dist/config/index.js +21 -0
  39. package/dist/config/index.js.map +1 -0
  40. package/dist/config/merge.d.ts +48 -0
  41. package/dist/config/merge.js +83 -0
  42. package/dist/config/merge.js.map +1 -0
  43. package/dist/config/schema.d.ts +254 -0
  44. package/dist/config/schema.js +109 -0
  45. package/dist/config/schema.js.map +1 -0
  46. package/dist/core/errors.d.ts +66 -0
  47. package/dist/core/errors.js +47 -0
  48. package/dist/core/errors.js.map +1 -0
  49. package/dist/core/gateway.d.ts +44 -0
  50. package/dist/core/gateway.js +400 -0
  51. package/dist/core/gateway.js.map +1 -0
  52. package/dist/core/health.d.ts +78 -0
  53. package/dist/core/health.js +65 -0
  54. package/dist/core/health.js.map +1 -0
  55. package/dist/core/pipeline.d.ts +62 -0
  56. package/dist/core/pipeline.js +214 -0
  57. package/dist/core/pipeline.js.map +1 -0
  58. package/dist/core/protocol.d.ts +4 -0
  59. package/dist/core/protocol.js +1 -0
  60. package/dist/core/protocol.js.map +1 -0
  61. package/dist/core/scope.d.ts +67 -0
  62. package/dist/core/scope.js +44 -0
  63. package/dist/core/scope.js.map +1 -0
  64. package/dist/core/types.d.ts +252 -0
  65. package/dist/core/types.js +1 -0
  66. package/dist/core/types.js.map +1 -0
  67. package/dist/index.d.ts +57 -0
  68. package/dist/index.js +158 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/observability/admin.d.ts +32 -0
  71. package/dist/observability/admin.js +85 -0
  72. package/dist/observability/admin.js.map +1 -0
  73. package/dist/observability/metrics.d.ts +78 -0
  74. package/dist/observability/metrics.js +107 -0
  75. package/dist/observability/metrics.js.map +1 -0
  76. package/dist/observability/tracing.d.ts +149 -0
  77. package/dist/observability/tracing.js +191 -0
  78. package/dist/observability/tracing.js.map +1 -0
  79. package/dist/policies/auth/api-key-auth.d.ts +64 -0
  80. package/dist/policies/auth/api-key-auth.js +93 -0
  81. package/dist/policies/auth/api-key-auth.js.map +1 -0
  82. package/dist/policies/auth/basic-auth.d.ts +33 -0
  83. package/dist/policies/auth/basic-auth.js +96 -0
  84. package/dist/policies/auth/basic-auth.js.map +1 -0
  85. package/dist/policies/auth/crypto.d.ts +29 -0
  86. package/dist/policies/auth/crypto.js +100 -0
  87. package/dist/policies/auth/crypto.js.map +1 -0
  88. package/dist/policies/auth/generate-http-signature.d.ts +30 -0
  89. package/dist/policies/auth/generate-http-signature.js +79 -0
  90. package/dist/policies/auth/generate-http-signature.js.map +1 -0
  91. package/dist/policies/auth/generate-jwt.d.ts +44 -0
  92. package/dist/policies/auth/generate-jwt.js +99 -0
  93. package/dist/policies/auth/generate-jwt.js.map +1 -0
  94. package/dist/policies/auth/http-signature-base.d.ts +55 -0
  95. package/dist/policies/auth/http-signature-base.js +140 -0
  96. package/dist/policies/auth/http-signature-base.js.map +1 -0
  97. package/dist/policies/auth/jws.d.ts +46 -0
  98. package/dist/policies/auth/jws.js +317 -0
  99. package/dist/policies/auth/jws.js.map +1 -0
  100. package/dist/policies/auth/jwt-auth.d.ts +64 -0
  101. package/dist/policies/auth/jwt-auth.js +266 -0
  102. package/dist/policies/auth/jwt-auth.js.map +1 -0
  103. package/dist/policies/auth/oauth2.d.ts +38 -0
  104. package/dist/policies/auth/oauth2.js +254 -0
  105. package/dist/policies/auth/oauth2.js.map +1 -0
  106. package/dist/policies/auth/rbac.d.ts +30 -0
  107. package/dist/policies/auth/rbac.js +115 -0
  108. package/dist/policies/auth/rbac.js.map +1 -0
  109. package/dist/policies/auth/verify-http-signature.d.ts +30 -0
  110. package/dist/policies/auth/verify-http-signature.js +147 -0
  111. package/dist/policies/auth/verify-http-signature.js.map +1 -0
  112. package/dist/policies/index.d.ts +51 -0
  113. package/dist/policies/index.js +109 -0
  114. package/dist/policies/index.js.map +1 -0
  115. package/dist/policies/mock.d.ts +60 -0
  116. package/dist/policies/mock.js +29 -0
  117. package/dist/policies/mock.js.map +1 -0
  118. package/dist/policies/observability/assign-metrics.d.ts +37 -0
  119. package/dist/policies/observability/assign-metrics.js +29 -0
  120. package/dist/policies/observability/assign-metrics.js.map +1 -0
  121. package/dist/policies/observability/metrics-reporter.d.ts +25 -0
  122. package/dist/policies/observability/metrics-reporter.js +62 -0
  123. package/dist/policies/observability/metrics-reporter.js.map +1 -0
  124. package/dist/policies/observability/request-log.d.ts +135 -0
  125. package/dist/policies/observability/request-log.js +134 -0
  126. package/dist/policies/observability/request-log.js.map +1 -0
  127. package/dist/policies/observability/server-timing.d.ts +35 -0
  128. package/dist/policies/observability/server-timing.js +89 -0
  129. package/dist/policies/observability/server-timing.js.map +1 -0
  130. package/dist/policies/proxy.d.ts +59 -0
  131. package/dist/policies/proxy.js +47 -0
  132. package/dist/policies/proxy.js.map +1 -0
  133. package/dist/policies/resilience/circuit-breaker.d.ts +4 -0
  134. package/dist/policies/resilience/circuit-breaker.js +280 -0
  135. package/dist/policies/resilience/circuit-breaker.js.map +1 -0
  136. package/dist/policies/resilience/latency-injection.d.ts +35 -0
  137. package/dist/policies/resilience/latency-injection.js +26 -0
  138. package/dist/policies/resilience/latency-injection.js.map +1 -0
  139. package/dist/policies/resilience/retry.d.ts +71 -0
  140. package/dist/policies/resilience/retry.js +79 -0
  141. package/dist/policies/resilience/retry.js.map +1 -0
  142. package/dist/policies/resilience/timeout.d.ts +32 -0
  143. package/dist/policies/resilience/timeout.js +46 -0
  144. package/dist/policies/resilience/timeout.js.map +1 -0
  145. package/dist/policies/sdk/define-policy.d.ts +176 -0
  146. package/dist/policies/sdk/define-policy.js +42 -0
  147. package/dist/policies/sdk/define-policy.js.map +1 -0
  148. package/dist/policies/sdk/helpers.d.ts +132 -0
  149. package/dist/policies/sdk/helpers.js +87 -0
  150. package/dist/policies/sdk/helpers.js.map +1 -0
  151. package/dist/policies/sdk/index.d.ts +10 -0
  152. package/dist/policies/sdk/index.js +35 -0
  153. package/dist/policies/sdk/index.js.map +1 -0
  154. package/dist/policies/sdk/priority.d.ts +44 -0
  155. package/dist/policies/sdk/priority.js +36 -0
  156. package/dist/policies/sdk/priority.js.map +1 -0
  157. package/dist/policies/sdk/testing.d.ts +53 -0
  158. package/dist/policies/sdk/testing.js +41 -0
  159. package/dist/policies/sdk/testing.js.map +1 -0
  160. package/dist/policies/sdk/trace.d.ts +73 -0
  161. package/dist/policies/sdk/trace.js +25 -0
  162. package/dist/policies/sdk/trace.js.map +1 -0
  163. package/dist/policies/traffic/cache.d.ts +4 -0
  164. package/dist/policies/traffic/cache.js +224 -0
  165. package/dist/policies/traffic/cache.js.map +1 -0
  166. package/dist/policies/traffic/dynamic-routing.d.ts +54 -0
  167. package/dist/policies/traffic/dynamic-routing.js +36 -0
  168. package/dist/policies/traffic/dynamic-routing.js.map +1 -0
  169. package/dist/policies/traffic/geo-ip-filter.d.ts +37 -0
  170. package/dist/policies/traffic/geo-ip-filter.js +74 -0
  171. package/dist/policies/traffic/geo-ip-filter.js.map +1 -0
  172. package/dist/policies/traffic/http-callout.d.ts +59 -0
  173. package/dist/policies/traffic/http-callout.js +69 -0
  174. package/dist/policies/traffic/http-callout.js.map +1 -0
  175. package/dist/policies/traffic/interrupt.d.ts +46 -0
  176. package/dist/policies/traffic/interrupt.js +38 -0
  177. package/dist/policies/traffic/interrupt.js.map +1 -0
  178. package/dist/policies/traffic/ip-filter.d.ts +47 -0
  179. package/dist/policies/traffic/ip-filter.js +57 -0
  180. package/dist/policies/traffic/ip-filter.js.map +1 -0
  181. package/dist/policies/traffic/json-threat-protection.d.ts +51 -0
  182. package/dist/policies/traffic/json-threat-protection.js +173 -0
  183. package/dist/policies/traffic/json-threat-protection.js.map +1 -0
  184. package/dist/policies/traffic/rate-limit.d.ts +4 -0
  185. package/dist/policies/traffic/rate-limit.js +145 -0
  186. package/dist/policies/traffic/rate-limit.js.map +1 -0
  187. package/dist/policies/traffic/regex-threat-protection.d.ts +54 -0
  188. package/dist/policies/traffic/regex-threat-protection.js +109 -0
  189. package/dist/policies/traffic/regex-threat-protection.js.map +1 -0
  190. package/dist/policies/traffic/request-limit.d.ts +27 -0
  191. package/dist/policies/traffic/request-limit.js +41 -0
  192. package/dist/policies/traffic/request-limit.js.map +1 -0
  193. package/dist/policies/traffic/resource-filter.d.ts +38 -0
  194. package/dist/policies/traffic/resource-filter.js +184 -0
  195. package/dist/policies/traffic/resource-filter.js.map +1 -0
  196. package/dist/policies/traffic/ssl-enforce.d.ts +27 -0
  197. package/dist/policies/traffic/ssl-enforce.js +38 -0
  198. package/dist/policies/traffic/ssl-enforce.js.map +1 -0
  199. package/dist/policies/traffic/traffic-shadow.d.ts +40 -0
  200. package/dist/policies/traffic/traffic-shadow.js +87 -0
  201. package/dist/policies/traffic/traffic-shadow.js.map +1 -0
  202. package/dist/policies/transform/assign-attributes.d.ts +33 -0
  203. package/dist/policies/transform/assign-attributes.js +38 -0
  204. package/dist/policies/transform/assign-attributes.js.map +1 -0
  205. package/dist/policies/transform/assign-content.d.ts +40 -0
  206. package/dist/policies/transform/assign-content.js +185 -0
  207. package/dist/policies/transform/assign-content.js.map +1 -0
  208. package/dist/policies/transform/cors.d.ts +57 -0
  209. package/dist/policies/transform/cors.js +23 -0
  210. package/dist/policies/transform/cors.js.map +1 -0
  211. package/dist/policies/transform/json-validation.d.ts +50 -0
  212. package/dist/policies/transform/json-validation.js +125 -0
  213. package/dist/policies/transform/json-validation.js.map +1 -0
  214. package/dist/policies/transform/override-method.d.ts +33 -0
  215. package/dist/policies/transform/override-method.js +48 -0
  216. package/dist/policies/transform/override-method.js.map +1 -0
  217. package/dist/policies/transform/request-validation.d.ts +59 -0
  218. package/dist/policies/transform/request-validation.js +121 -0
  219. package/dist/policies/transform/request-validation.js.map +1 -0
  220. package/dist/policies/transform/transform.d.ts +75 -0
  221. package/dist/policies/transform/transform.js +116 -0
  222. package/dist/policies/transform/transform.js.map +1 -0
  223. package/dist/policies/types.d.ts +4 -0
  224. package/dist/policies/types.js +1 -0
  225. package/dist/policies/types.js.map +1 -0
  226. package/dist/protocol-2fD3DJrL.d.ts +725 -0
  227. package/dist/utils/cidr.d.ts +58 -0
  228. package/dist/utils/cidr.js +107 -0
  229. package/dist/utils/cidr.js.map +1 -0
  230. package/dist/utils/debug.d.ts +1 -0
  231. package/dist/utils/debug.js +13 -0
  232. package/dist/utils/debug.js.map +1 -0
  233. package/dist/utils/headers.d.ts +68 -0
  234. package/dist/utils/headers.js +25 -0
  235. package/dist/utils/headers.js.map +1 -0
  236. package/dist/utils/ip.d.ts +64 -0
  237. package/dist/utils/ip.js +29 -0
  238. package/dist/utils/ip.js.map +1 -0
  239. package/dist/utils/redact.d.ts +30 -0
  240. package/dist/utils/redact.js +52 -0
  241. package/dist/utils/redact.js.map +1 -0
  242. package/dist/utils/request-id.d.ts +11 -0
  243. package/dist/utils/request-id.js +7 -0
  244. package/dist/utils/request-id.js.map +1 -0
  245. package/dist/utils/timing-safe.d.ts +31 -0
  246. package/dist/utils/timing-safe.js +17 -0
  247. package/dist/utils/timing-safe.js.map +1 -0
  248. package/dist/utils/timing.d.ts +27 -0
  249. package/dist/utils/timing.js +12 -0
  250. package/dist/utils/timing.js.map +1 -0
  251. package/dist/utils/trace-context.d.ts +51 -0
  252. package/dist/utils/trace-context.js +37 -0
  253. package/dist/utils/trace-context.js.map +1 -0
  254. package/package.json +213 -0
@@ -0,0 +1,74 @@
1
+ import { GatewayError } from "../../core/errors";
2
+ import { Priority, policyDebug, resolveConfig, withSkip } from "../sdk";
3
+ function geoIpFilter(config) {
4
+ const resolved = resolveConfig(
5
+ { mode: "deny", countryHeader: "cf-ipcountry" },
6
+ config
7
+ );
8
+ const allowSet = new Set(
9
+ (resolved.allow ?? []).map((code) => code.toUpperCase())
10
+ );
11
+ const denySet = new Set(
12
+ (resolved.deny ?? []).map((code) => code.toUpperCase())
13
+ );
14
+ const handler = async (c, next) => {
15
+ const debug = policyDebug(c, "geo-ip-filter");
16
+ const country = c.req.header(resolved.countryHeader)?.toUpperCase();
17
+ const mode = resolved.mode;
18
+ debug(`country=${country ?? "unknown"} mode=${mode}`);
19
+ if (mode === "allow") {
20
+ if (!country || !allowSet.has(country)) {
21
+ throw new GatewayError(
22
+ 403,
23
+ "geo_denied",
24
+ "Access denied from this region"
25
+ );
26
+ }
27
+ } else {
28
+ if (country && denySet.has(country)) {
29
+ throw new GatewayError(
30
+ 403,
31
+ "geo_denied",
32
+ "Access denied from this region"
33
+ );
34
+ }
35
+ }
36
+ await next();
37
+ };
38
+ return {
39
+ name: "geo-ip-filter",
40
+ priority: Priority.IP_FILTER,
41
+ handler: withSkip(config?.skip, handler),
42
+ phases: ["request-headers"],
43
+ evaluate: {
44
+ onRequest: async (input) => {
45
+ const country = input.headers.get(resolved.countryHeader)?.toUpperCase();
46
+ const mode = resolved.mode;
47
+ if (mode === "allow") {
48
+ if (!country || !allowSet.has(country)) {
49
+ return {
50
+ action: "reject",
51
+ status: 403,
52
+ code: "geo_denied",
53
+ message: "Access denied from this region"
54
+ };
55
+ }
56
+ } else {
57
+ if (country && denySet.has(country)) {
58
+ return {
59
+ action: "reject",
60
+ status: 403,
61
+ code: "geo_denied",
62
+ message: "Access denied from this region"
63
+ };
64
+ }
65
+ }
66
+ return { action: "continue" };
67
+ }
68
+ }
69
+ };
70
+ }
71
+ export {
72
+ geoIpFilter
73
+ };
74
+ //# sourceMappingURL=geo-ip-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/traffic/geo-ip-filter.ts"],"sourcesContent":["/**\n * Geographic IP filtering policy.\n *\n * Allows or denies requests based on the country code derived from\n * a configurable header (defaults to Cloudflare's `cf-ipcountry`).\n *\n * @module geo-ip-filter\n */\nimport { GatewayError } from \"../../core/errors\";\nimport type { PolicyInput } from \"../../core/protocol\";\nimport { Priority, policyDebug, resolveConfig, withSkip } from \"../sdk\";\nimport type { Policy, PolicyConfig } from \"../types\";\n\nexport interface GeoIpFilterConfig extends PolicyConfig {\n /** Country codes to allow (e.g. `[\"US\", \"CA\", \"GB\"]`). Used in \"allow\" mode. */\n allow?: string[];\n /** Country codes to deny. Used in \"deny\" mode. */\n deny?: string[];\n /** Filter mode. Default: `\"deny\"`. */\n mode?: \"allow\" | \"deny\";\n /** Header name to read the country code from. Default: `\"cf-ipcountry\"`. */\n countryHeader?: string;\n}\n\n/**\n * Block or allow requests based on geographic country code.\n *\n * Reads the country from the configured header (default `cf-ipcountry`,\n * set by Cloudflare). Supports allowlist and denylist modes. Country\n * sets are pre-computed once at construction time for efficiency.\n *\n * @param config - Country filter rules and mode selection.\n * @returns A policy at priority 1 (IP_FILTER).\n *\n * @example\n * ```ts\n * // Allow only US, Canada, and UK\n * geoIpFilter({ mode: \"allow\", allow: [\"US\", \"CA\", \"GB\"] });\n *\n * // Block specific countries\n * geoIpFilter({ deny: [\"CN\", \"RU\"] });\n * ```\n */\nexport function geoIpFilter(config?: GeoIpFilterConfig): Policy {\n const resolved = resolveConfig<GeoIpFilterConfig>(\n { mode: \"deny\", countryHeader: \"cf-ipcountry\" },\n config\n );\n\n // Pre-compute country sets once at construction time instead of per-request\n const allowSet = new Set(\n (resolved.allow ?? []).map((code) => code.toUpperCase())\n );\n const denySet = new Set(\n (resolved.deny ?? []).map((code) => code.toUpperCase())\n );\n\n const handler: import(\"hono\").MiddlewareHandler = async (c, next) => {\n const debug = policyDebug(c, \"geo-ip-filter\");\n const country = c.req.header(resolved.countryHeader!)?.toUpperCase();\n const mode = resolved.mode!;\n\n debug(`country=${country ?? \"unknown\"} mode=${mode}`);\n\n if (mode === \"allow\") {\n // Unknown country (missing header) is denied in allow mode\n if (!country || !allowSet.has(country)) {\n throw new GatewayError(\n 403,\n \"geo_denied\",\n \"Access denied from this region\"\n );\n }\n } else {\n // Unknown country (missing header) is allowed in deny mode\n if (country && denySet.has(country)) {\n throw new GatewayError(\n 403,\n \"geo_denied\",\n \"Access denied from this region\"\n );\n }\n }\n\n await next();\n };\n\n return {\n name: \"geo-ip-filter\",\n priority: Priority.IP_FILTER,\n handler: withSkip(config?.skip, handler),\n phases: [\"request-headers\"],\n evaluate: {\n onRequest: async (input: PolicyInput) => {\n const country = input.headers\n .get(resolved.countryHeader!)\n ?.toUpperCase();\n const mode = resolved.mode!;\n\n if (mode === \"allow\") {\n if (!country || !allowSet.has(country)) {\n return {\n action: \"reject\",\n status: 403,\n code: \"geo_denied\",\n message: \"Access denied from this region\",\n };\n }\n } else {\n if (country && denySet.has(country)) {\n return {\n action: \"reject\",\n status: 403,\n code: \"geo_denied\",\n message: \"Access denied from this region\",\n };\n }\n }\n\n return { action: \"continue\" };\n },\n },\n };\n}\n"],"mappings":"AAQA,SAAS,oBAAoB;AAE7B,SAAS,UAAU,aAAa,eAAe,gBAAgB;AAiCxD,SAAS,YAAY,QAAoC;AAC9D,QAAM,WAAW;AAAA,IACf,EAAE,MAAM,QAAQ,eAAe,eAAe;AAAA,IAC9C;AAAA,EACF;AAGA,QAAM,WAAW,IAAI;AAAA,KAClB,SAAS,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,KAAK,YAAY,CAAC;AAAA,EACzD;AACA,QAAM,UAAU,IAAI;AAAA,KACjB,SAAS,QAAQ,CAAC,GAAG,IAAI,CAAC,SAAS,KAAK,YAAY,CAAC;AAAA,EACxD;AAEA,QAAM,UAA4C,OAAO,GAAG,SAAS;AACnE,UAAM,QAAQ,YAAY,GAAG,eAAe;AAC5C,UAAM,UAAU,EAAE,IAAI,OAAO,SAAS,aAAc,GAAG,YAAY;AACnE,UAAM,OAAO,SAAS;AAEtB,UAAM,WAAW,WAAW,SAAS,SAAS,IAAI,EAAE;AAEpD,QAAI,SAAS,SAAS;AAEpB,UAAI,CAAC,WAAW,CAAC,SAAS,IAAI,OAAO,GAAG;AACtC,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AAEL,UAAI,WAAW,QAAQ,IAAI,OAAO,GAAG;AACnC,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK;AAAA,EACb;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,SAAS;AAAA,IACnB,SAAS,SAAS,QAAQ,MAAM,OAAO;AAAA,IACvC,QAAQ,CAAC,iBAAiB;AAAA,IAC1B,UAAU;AAAA,MACR,WAAW,OAAO,UAAuB;AACvC,cAAM,UAAU,MAAM,QACnB,IAAI,SAAS,aAAc,GAC1B,YAAY;AAChB,cAAM,OAAO,SAAS;AAEtB,YAAI,SAAS,SAAS;AACpB,cAAI,CAAC,WAAW,CAAC,SAAS,IAAI,OAAO,GAAG;AACtC,mBAAO;AAAA,cACL,QAAQ;AAAA,cACR,QAAQ;AAAA,cACR,MAAM;AAAA,cACN,SAAS;AAAA,YACX;AAAA,UACF;AAAA,QACF,OAAO;AACL,cAAI,WAAW,QAAQ,IAAI,OAAO,GAAG;AACnC,mBAAO;AAAA,cACL,QAAQ;AAAA,cACR,QAAQ;AAAA,cACR,MAAM;AAAA,cACN,SAAS;AAAA,YACX;AAAA,UACF;AAAA,QACF;AAEA,eAAO,EAAE,QAAQ,WAAW;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,59 @@
1
+ import { g as PolicyConfig, P as Policy } from '../../protocol-2fD3DJrL.js';
2
+ import { Context } from 'hono';
3
+ import '../sdk/trace.js';
4
+ import '@vivero/stoma-core';
5
+
6
+ interface HttpCalloutConfig extends PolicyConfig {
7
+ /** Target URL - static string or dynamic function. Required. */
8
+ url: string | ((c: Context) => string | Promise<string>);
9
+ /** HTTP method. Default: "GET". */
10
+ method?: string;
11
+ /** Request headers - static values or dynamic functions. */
12
+ headers?: Record<string, string | ((c: Context) => string | Promise<string>)>;
13
+ /** Request body - static or dynamic. JSON-serialized if object. */
14
+ body?: unknown | ((c: Context) => unknown | Promise<unknown>);
15
+ /** Timeout in ms. Default: 5000. */
16
+ timeout?: number;
17
+ /** Callback to process the callout response. Required. */
18
+ onResponse: (response: Response, c: Context) => void | Promise<void>;
19
+ /** Error handler. Default: throw GatewayError 502. */
20
+ onError?: (error: unknown, c: Context) => void | Promise<void>;
21
+ /** If true, throw on non-2xx response. Default: true. */
22
+ abortOnFailure?: boolean;
23
+ }
24
+ /**
25
+ * Make an external HTTP call mid-pipeline.
26
+ *
27
+ * Resolves URL, headers, and body (static or dynamic), makes the fetch,
28
+ * and calls the `onResponse` callback to process the result. Errors are
29
+ * handled via `onError` or default to a 502 GatewayError.
30
+ *
31
+ * @security When the `url` parameter is a dynamic function that derives
32
+ * the callout target from request data (headers, path, query, or body),
33
+ * this policy is vulnerable to Server-Side Request Forgery (SSRF). An
34
+ * attacker could manipulate request data to make the worker issue requests
35
+ * to internal services, metadata endpoints (e.g. cloud provider instance
36
+ * metadata), or other unintended targets. Hardcode callout URLs whenever
37
+ * possible. If dynamic URLs are required, validate them against an
38
+ * explicit allowlist of permitted hosts and schemes.
39
+ *
40
+ * @param config - Callout target, method, headers, body, and response handler.
41
+ * @returns A {@link Policy} at priority 50 (REQUEST_TRANSFORM).
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * httpCallout({
46
+ * url: "https://auth.example.com/validate",
47
+ * method: "POST",
48
+ * headers: { authorization: (c) => c.req.header("authorization") ?? "" },
49
+ * body: (c) => ({ path: c.req.path }),
50
+ * onResponse: async (res, c) => {
51
+ * const data = await res.json();
52
+ * c.set("userId", data.userId);
53
+ * },
54
+ * });
55
+ * ```
56
+ */
57
+ declare const httpCallout: (config: HttpCalloutConfig) => Policy;
58
+
59
+ export { type HttpCalloutConfig, httpCallout };
@@ -0,0 +1,69 @@
1
+ import { GatewayError } from "../../core/errors";
2
+ import { definePolicy, Priority } from "../sdk";
3
+ const httpCallout = /* @__PURE__ */ definePolicy({
4
+ name: "http-callout",
5
+ priority: Priority.REQUEST_TRANSFORM,
6
+ httpOnly: true,
7
+ defaults: { method: "GET", timeout: 5e3, abortOnFailure: true },
8
+ handler: async (c, next, { config, debug }) => {
9
+ const url = typeof config.url === "function" ? await config.url(c) : config.url;
10
+ const resolvedHeaders = {};
11
+ if (config.headers) {
12
+ for (const [key, value] of Object.entries(config.headers)) {
13
+ resolvedHeaders[key] = typeof value === "function" ? await value(c) : value;
14
+ }
15
+ }
16
+ let resolvedBody;
17
+ if (config.body !== void 0) {
18
+ const raw = typeof config.body === "function" ? await config.body(c) : config.body;
19
+ if (raw !== void 0 && raw !== null) {
20
+ resolvedBody = typeof raw === "string" ? raw : JSON.stringify(raw);
21
+ if (typeof raw !== "string" && !resolvedHeaders["content-type"]) {
22
+ resolvedHeaders["content-type"] = "application/json";
23
+ }
24
+ }
25
+ }
26
+ debug(`${config.method} ${url}`);
27
+ let response;
28
+ try {
29
+ response = await fetch(url, {
30
+ method: config.method,
31
+ headers: resolvedHeaders,
32
+ body: resolvedBody,
33
+ signal: AbortSignal.timeout(config.timeout)
34
+ });
35
+ } catch (error) {
36
+ if (config.onError) {
37
+ await config.onError(error, c);
38
+ await next();
39
+ return;
40
+ }
41
+ throw new GatewayError(
42
+ 502,
43
+ "callout_failed",
44
+ `External callout failed: ${error instanceof Error ? error.message : String(error)}`
45
+ );
46
+ }
47
+ if (!response.ok && config.abortOnFailure) {
48
+ if (config.onError) {
49
+ await config.onError(
50
+ new Error(`External callout returned ${response.status}`),
51
+ c
52
+ );
53
+ await next();
54
+ return;
55
+ }
56
+ throw new GatewayError(
57
+ 502,
58
+ "callout_failed",
59
+ `External callout returned ${response.status}`
60
+ );
61
+ }
62
+ await config.onResponse(response, c);
63
+ await next();
64
+ }
65
+ });
66
+ export {
67
+ httpCallout
68
+ };
69
+ //# sourceMappingURL=http-callout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/traffic/http-callout.ts"],"sourcesContent":["/**\n * HTTP callout policy - make an external HTTP call mid-pipeline.\n *\n * Use cases include external authorization, token exchange, data enrichment,\n * and webhook notification before proxying to the upstream service.\n *\n * @module http-callout\n */\nimport type { Context } from \"hono\";\nimport { GatewayError } from \"../../core/errors\";\nimport { definePolicy, Priority } from \"../sdk\";\nimport type { PolicyConfig } from \"../types\";\n\nexport interface HttpCalloutConfig extends PolicyConfig {\n /** Target URL - static string or dynamic function. Required. */\n url: string | ((c: Context) => string | Promise<string>);\n /** HTTP method. Default: \"GET\". */\n method?: string;\n /** Request headers - static values or dynamic functions. */\n headers?: Record<string, string | ((c: Context) => string | Promise<string>)>;\n /** Request body - static or dynamic. JSON-serialized if object. */\n body?: unknown | ((c: Context) => unknown | Promise<unknown>);\n /** Timeout in ms. Default: 5000. */\n timeout?: number;\n /** Callback to process the callout response. Required. */\n onResponse: (response: Response, c: Context) => void | Promise<void>;\n /** Error handler. Default: throw GatewayError 502. */\n onError?: (error: unknown, c: Context) => void | Promise<void>;\n /** If true, throw on non-2xx response. Default: true. */\n abortOnFailure?: boolean;\n}\n\n/**\n * Make an external HTTP call mid-pipeline.\n *\n * Resolves URL, headers, and body (static or dynamic), makes the fetch,\n * and calls the `onResponse` callback to process the result. Errors are\n * handled via `onError` or default to a 502 GatewayError.\n *\n * @security When the `url` parameter is a dynamic function that derives\n * the callout target from request data (headers, path, query, or body),\n * this policy is vulnerable to Server-Side Request Forgery (SSRF). An\n * attacker could manipulate request data to make the worker issue requests\n * to internal services, metadata endpoints (e.g. cloud provider instance\n * metadata), or other unintended targets. Hardcode callout URLs whenever\n * possible. If dynamic URLs are required, validate them against an\n * explicit allowlist of permitted hosts and schemes.\n *\n * @param config - Callout target, method, headers, body, and response handler.\n * @returns A {@link Policy} at priority 50 (REQUEST_TRANSFORM).\n *\n * @example\n * ```ts\n * httpCallout({\n * url: \"https://auth.example.com/validate\",\n * method: \"POST\",\n * headers: { authorization: (c) => c.req.header(\"authorization\") ?? \"\" },\n * body: (c) => ({ path: c.req.path }),\n * onResponse: async (res, c) => {\n * const data = await res.json();\n * c.set(\"userId\", data.userId);\n * },\n * });\n * ```\n */\nexport const httpCallout = /*#__PURE__*/ definePolicy<HttpCalloutConfig>({\n name: \"http-callout\",\n priority: Priority.REQUEST_TRANSFORM,\n httpOnly: true,\n defaults: { method: \"GET\", timeout: 5000, abortOnFailure: true },\n handler: async (c, next, { config, debug }) => {\n // Resolve URL\n const url =\n typeof config.url === \"function\" ? await config.url(c) : config.url;\n\n // Resolve headers\n const resolvedHeaders: Record<string, string> = {};\n if (config.headers) {\n for (const [key, value] of Object.entries(config.headers)) {\n resolvedHeaders[key] =\n typeof value === \"function\" ? await value(c) : value;\n }\n }\n\n // Resolve body\n let resolvedBody: string | undefined;\n if (config.body !== undefined) {\n const raw =\n typeof config.body === \"function\"\n ? await (config.body as (c: Context) => unknown | Promise<unknown>)(c)\n : config.body;\n if (raw !== undefined && raw !== null) {\n resolvedBody = typeof raw === \"string\" ? raw : JSON.stringify(raw);\n if (typeof raw !== \"string\" && !resolvedHeaders[\"content-type\"]) {\n resolvedHeaders[\"content-type\"] = \"application/json\";\n }\n }\n }\n\n debug(`${config.method} ${url}`);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: config.method,\n headers: resolvedHeaders,\n body: resolvedBody,\n signal: AbortSignal.timeout(config.timeout!),\n });\n } catch (error) {\n if (config.onError) {\n await config.onError(error, c);\n await next();\n return;\n }\n throw new GatewayError(\n 502,\n \"callout_failed\",\n `External callout failed: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n // Check for non-2xx\n if (!response.ok && config.abortOnFailure) {\n if (config.onError) {\n await config.onError(\n new Error(`External callout returned ${response.status}`),\n c\n );\n await next();\n return;\n }\n throw new GatewayError(\n 502,\n \"callout_failed\",\n `External callout returned ${response.status}`\n );\n }\n\n await config.onResponse(response, c);\n await next();\n },\n});\n"],"mappings":"AASA,SAAS,oBAAoB;AAC7B,SAAS,cAAc,gBAAgB;AAuDhC,MAAM,cAA4B,6BAAgC;AAAA,EACvE,MAAM;AAAA,EACN,UAAU,SAAS;AAAA,EACnB,UAAU;AAAA,EACV,UAAU,EAAE,QAAQ,OAAO,SAAS,KAAM,gBAAgB,KAAK;AAAA,EAC/D,SAAS,OAAO,GAAG,MAAM,EAAE,QAAQ,MAAM,MAAM;AAE7C,UAAM,MACJ,OAAO,OAAO,QAAQ,aAAa,MAAM,OAAO,IAAI,CAAC,IAAI,OAAO;AAGlE,UAAM,kBAA0C,CAAC;AACjD,QAAI,OAAO,SAAS;AAClB,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG;AACzD,wBAAgB,GAAG,IACjB,OAAO,UAAU,aAAa,MAAM,MAAM,CAAC,IAAI;AAAA,MACnD;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,OAAO,SAAS,QAAW;AAC7B,YAAM,MACJ,OAAO,OAAO,SAAS,aACnB,MAAO,OAAO,KAAoD,CAAC,IACnE,OAAO;AACb,UAAI,QAAQ,UAAa,QAAQ,MAAM;AACrC,uBAAe,OAAO,QAAQ,WAAW,MAAM,KAAK,UAAU,GAAG;AACjE,YAAI,OAAO,QAAQ,YAAY,CAAC,gBAAgB,cAAc,GAAG;AAC/D,0BAAgB,cAAc,IAAI;AAAA,QACpC;AAAA,MACF;AAAA,IACF;AAEA,UAAM,GAAG,OAAO,MAAM,IAAI,GAAG,EAAE;AAE/B,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,KAAK;AAAA,QAC1B,QAAQ,OAAO;AAAA,QACf,SAAS;AAAA,QACT,MAAM;AAAA,QACN,QAAQ,YAAY,QAAQ,OAAO,OAAQ;AAAA,MAC7C,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,OAAO,SAAS;AAClB,cAAM,OAAO,QAAQ,OAAO,CAAC;AAC7B,cAAM,KAAK;AACX;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA,4BAA4B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MACpF;AAAA,IACF;AAGA,QAAI,CAAC,SAAS,MAAM,OAAO,gBAAgB;AACzC,UAAI,OAAO,SAAS;AAClB,cAAM,OAAO;AAAA,UACX,IAAI,MAAM,6BAA6B,SAAS,MAAM,EAAE;AAAA,UACxD;AAAA,QACF;AACA,cAAM,KAAK;AACX;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA,6BAA6B,SAAS,MAAM;AAAA,MAC9C;AAAA,IACF;AAEA,UAAM,OAAO,WAAW,UAAU,CAAC;AACnC,UAAM,KAAK;AAAA,EACb;AACF,CAAC;","names":[]}
@@ -0,0 +1,46 @@
1
+ import { g as PolicyConfig, P as Policy } from '../../protocol-2fD3DJrL.js';
2
+ import { Context } from 'hono';
3
+ import '../sdk/trace.js';
4
+ import '@vivero/stoma-core';
5
+
6
+ interface InterruptConfig extends PolicyConfig {
7
+ /** Predicate that determines whether to short-circuit. Required. */
8
+ condition: (c: Context) => boolean | Promise<boolean>;
9
+ /** HTTP status code for the interrupt response. Default: 200. */
10
+ statusCode?: number;
11
+ /** Response body. String → text/plain, object → application/json, undefined → empty. */
12
+ body?: unknown;
13
+ /** Additional response headers. */
14
+ headers?: Record<string, string>;
15
+ }
16
+ /**
17
+ * Conditionally short-circuit the pipeline and return a static response.
18
+ *
19
+ * Evaluates a predicate against the incoming request context. When the
20
+ * condition returns `true`, the pipeline is interrupted - a response is
21
+ * returned immediately and `next()` is never called (upstream is skipped).
22
+ * When the condition returns `false`, the pipeline continues normally.
23
+ *
24
+ * @param config - Condition predicate, status code, body, and headers.
25
+ * @returns A {@link Policy} at priority 100 (default - users typically set a custom priority).
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Maintenance mode
30
+ * interrupt({
31
+ * condition: (c) => c.req.header("x-maintenance") === "true",
32
+ * statusCode: 503,
33
+ * body: { maintenance: true, message: "Back soon" },
34
+ * headers: { "retry-after": "300" },
35
+ * });
36
+ *
37
+ * // Health check short-circuit
38
+ * interrupt({
39
+ * condition: (c) => c.req.path === "/healthz",
40
+ * body: "ok",
41
+ * });
42
+ * ```
43
+ */
44
+ declare const interrupt: (config: InterruptConfig) => Policy;
45
+
46
+ export { type InterruptConfig, interrupt };
@@ -0,0 +1,38 @@
1
+ import { definePolicy, Priority } from "../sdk";
2
+ const interrupt = /* @__PURE__ */ definePolicy({
3
+ name: "interrupt",
4
+ priority: Priority.DEFAULT,
5
+ httpOnly: true,
6
+ defaults: { statusCode: 200, headers: {} },
7
+ handler: async (c, next, { config, debug }) => {
8
+ const shouldInterrupt = await config.condition(c);
9
+ if (!shouldInterrupt) {
10
+ debug("condition false, continuing pipeline");
11
+ await next();
12
+ return;
13
+ }
14
+ debug("condition true, short-circuiting");
15
+ const responseHeaders = new Headers(config.headers);
16
+ let body = null;
17
+ if (config.body === void 0 || config.body === null) {
18
+ } else if (typeof config.body === "string") {
19
+ body = config.body;
20
+ if (!responseHeaders.has("content-type")) {
21
+ responseHeaders.set("content-type", "text/plain");
22
+ }
23
+ } else {
24
+ body = JSON.stringify(config.body);
25
+ if (!responseHeaders.has("content-type")) {
26
+ responseHeaders.set("content-type", "application/json");
27
+ }
28
+ }
29
+ c.res = new Response(body, {
30
+ status: config.statusCode,
31
+ headers: responseHeaders
32
+ });
33
+ }
34
+ });
35
+ export {
36
+ interrupt
37
+ };
38
+ //# sourceMappingURL=interrupt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/traffic/interrupt.ts"],"sourcesContent":["/**\n * Interrupt policy - conditionally short-circuit the pipeline with a static response.\n *\n * @module interrupt\n */\nimport type { Context } from \"hono\";\nimport { definePolicy, Priority } from \"../sdk\";\nimport type { PolicyConfig } from \"../types\";\n\nexport interface InterruptConfig extends PolicyConfig {\n /** Predicate that determines whether to short-circuit. Required. */\n condition: (c: Context) => boolean | Promise<boolean>;\n /** HTTP status code for the interrupt response. Default: 200. */\n statusCode?: number;\n /** Response body. String → text/plain, object → application/json, undefined → empty. */\n body?: unknown;\n /** Additional response headers. */\n headers?: Record<string, string>;\n}\n\n/**\n * Conditionally short-circuit the pipeline and return a static response.\n *\n * Evaluates a predicate against the incoming request context. When the\n * condition returns `true`, the pipeline is interrupted - a response is\n * returned immediately and `next()` is never called (upstream is skipped).\n * When the condition returns `false`, the pipeline continues normally.\n *\n * @param config - Condition predicate, status code, body, and headers.\n * @returns A {@link Policy} at priority 100 (default - users typically set a custom priority).\n *\n * @example\n * ```ts\n * // Maintenance mode\n * interrupt({\n * condition: (c) => c.req.header(\"x-maintenance\") === \"true\",\n * statusCode: 503,\n * body: { maintenance: true, message: \"Back soon\" },\n * headers: { \"retry-after\": \"300\" },\n * });\n *\n * // Health check short-circuit\n * interrupt({\n * condition: (c) => c.req.path === \"/healthz\",\n * body: \"ok\",\n * });\n * ```\n */\nexport const interrupt = /*#__PURE__*/ definePolicy<InterruptConfig>({\n name: \"interrupt\",\n priority: Priority.DEFAULT,\n httpOnly: true,\n defaults: { statusCode: 200, headers: {} },\n handler: async (c, next, { config, debug }) => {\n const shouldInterrupt = await config.condition(c);\n\n if (!shouldInterrupt) {\n debug(\"condition false, continuing pipeline\");\n await next();\n return;\n }\n\n debug(\"condition true, short-circuiting\");\n\n const responseHeaders = new Headers(config.headers);\n\n let body: string | null = null;\n if (config.body === undefined || config.body === null) {\n // empty body\n } else if (typeof config.body === \"string\") {\n body = config.body;\n if (!responseHeaders.has(\"content-type\")) {\n responseHeaders.set(\"content-type\", \"text/plain\");\n }\n } else {\n body = JSON.stringify(config.body);\n if (!responseHeaders.has(\"content-type\")) {\n responseHeaders.set(\"content-type\", \"application/json\");\n }\n }\n\n c.res = new Response(body, {\n status: config.statusCode!,\n headers: responseHeaders,\n });\n },\n});\n"],"mappings":"AAMA,SAAS,cAAc,gBAAgB;AA0ChC,MAAM,YAA0B,6BAA8B;AAAA,EACnE,MAAM;AAAA,EACN,UAAU,SAAS;AAAA,EACnB,UAAU;AAAA,EACV,UAAU,EAAE,YAAY,KAAK,SAAS,CAAC,EAAE;AAAA,EACzC,SAAS,OAAO,GAAG,MAAM,EAAE,QAAQ,MAAM,MAAM;AAC7C,UAAM,kBAAkB,MAAM,OAAO,UAAU,CAAC;AAEhD,QAAI,CAAC,iBAAiB;AACpB,YAAM,sCAAsC;AAC5C,YAAM,KAAK;AACX;AAAA,IACF;AAEA,UAAM,kCAAkC;AAExC,UAAM,kBAAkB,IAAI,QAAQ,OAAO,OAAO;AAElD,QAAI,OAAsB;AAC1B,QAAI,OAAO,SAAS,UAAa,OAAO,SAAS,MAAM;AAAA,IAEvD,WAAW,OAAO,OAAO,SAAS,UAAU;AAC1C,aAAO,OAAO;AACd,UAAI,CAAC,gBAAgB,IAAI,cAAc,GAAG;AACxC,wBAAgB,IAAI,gBAAgB,YAAY;AAAA,MAClD;AAAA,IACF,OAAO;AACL,aAAO,KAAK,UAAU,OAAO,IAAI;AACjC,UAAI,CAAC,gBAAgB,IAAI,cAAc,GAAG;AACxC,wBAAgB,IAAI,gBAAgB,kBAAkB;AAAA,MACxD;AAAA,IACF;AAEA,MAAE,MAAM,IAAI,SAAS,MAAM;AAAA,MACzB,QAAQ,OAAO;AAAA,MACf,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF,CAAC;","names":[]}
@@ -0,0 +1,47 @@
1
+ import { g as PolicyConfig, P as Policy } from '../../protocol-2fD3DJrL.js';
2
+ import 'hono';
3
+ import '../sdk/trace.js';
4
+ import '@vivero/stoma-core';
5
+
6
+ /**
7
+ * IP allowlist/denylist filtering policy.
8
+ *
9
+ * Supports both HTTP (`handler`) and protocol-agnostic (`evaluate`) entry
10
+ * points. The evaluate path uses {@link PolicyInput.clientIp} when available
11
+ * (set by the runtime), falling back to header extraction.
12
+ *
13
+ * @module ip-filter
14
+ */
15
+
16
+ interface IpFilterConfig extends PolicyConfig {
17
+ /** IPs or CIDR ranges to allow (allowlist mode). */
18
+ allow?: string[];
19
+ /** IPs or CIDR ranges to deny (denylist mode). */
20
+ deny?: string[];
21
+ /** Filter mode. Default: "deny". */
22
+ mode?: "allow" | "deny";
23
+ /** Ordered list of headers to inspect for the client IP. Default: `["cf-connecting-ip", "x-forwarded-for"]`. */
24
+ ipHeaders?: string[];
25
+ }
26
+ /**
27
+ * Block or allow requests based on client IP address or CIDR range.
28
+ *
29
+ * Supports both allowlist and denylist modes. Client IP is extracted from
30
+ * `CF-Connecting-IP` (Cloudflare) or `X-Forwarded-For`. Accepts individual
31
+ * IPs (`192.168.1.1`) and CIDR notation (`10.0.0.0/8`).
32
+ *
33
+ * @param config - IP filter rules and mode selection.
34
+ * @returns A {@link Policy} at priority 1 (runs before everything else).
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * // Allow only internal IPs
39
+ * ipFilter({ mode: "allow", allow: ["10.0.0.0/8", "172.16.0.0/12"] });
40
+ *
41
+ * // Block known bad actors
42
+ * ipFilter({ deny: ["203.0.113.0/24", "198.51.100.42"] });
43
+ * ```
44
+ */
45
+ declare function ipFilter(config: IpFilterConfig): Policy;
46
+
47
+ export { type IpFilterConfig, ipFilter };
@@ -0,0 +1,57 @@
1
+ import { GatewayError } from "../../core/errors";
2
+ import { isInRange, parseCIDR } from "../../utils/cidr";
3
+ import { extractClientIp } from "../../utils/ip";
4
+ import { Priority, withSkip } from "../sdk";
5
+ function ipFilter(config) {
6
+ const mode = config.mode ?? "deny";
7
+ const ipHeaderOptions = config.ipHeaders ? { ipHeaders: config.ipHeaders } : {};
8
+ const allowRanges = (config.allow ?? []).map(parseCIDR).filter((r) => r !== null);
9
+ const denyRanges = (config.deny ?? []).map(parseCIDR).filter((r) => r !== null);
10
+ function checkIp(ip) {
11
+ if (mode === "allow") {
12
+ if (!isInRange(ip, allowRanges)) {
13
+ return {
14
+ action: "reject",
15
+ status: 403,
16
+ code: "ip_denied",
17
+ message: "Access denied"
18
+ };
19
+ }
20
+ } else {
21
+ if (isInRange(ip, denyRanges)) {
22
+ return {
23
+ action: "reject",
24
+ status: 403,
25
+ code: "ip_denied",
26
+ message: "Access denied"
27
+ };
28
+ }
29
+ }
30
+ return { action: "continue" };
31
+ }
32
+ const handler = async (c, next) => {
33
+ const ip = extractClientIp(c.req.raw.headers, ipHeaderOptions);
34
+ const result = checkIp(ip);
35
+ if (result.action === "reject") {
36
+ throw new GatewayError(result.status, result.code, result.message);
37
+ }
38
+ await next();
39
+ };
40
+ return {
41
+ name: "ip-filter",
42
+ priority: Priority.IP_FILTER,
43
+ handler: withSkip(config.skip, handler),
44
+ phases: ["request-headers"],
45
+ // ── Protocol-agnostic evaluator ────────────────────────────────
46
+ evaluate: {
47
+ onRequest: async (input) => {
48
+ const ip = input.clientIp ?? extractClientIp(input.headers, ipHeaderOptions);
49
+ return checkIp(ip);
50
+ }
51
+ }
52
+ };
53
+ }
54
+ export {
55
+ ipFilter
56
+ };
57
+ //# sourceMappingURL=ip-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/policies/traffic/ip-filter.ts"],"sourcesContent":["/**\n * IP allowlist/denylist filtering policy.\n *\n * Supports both HTTP (`handler`) and protocol-agnostic (`evaluate`) entry\n * points. The evaluate path uses {@link PolicyInput.clientIp} when available\n * (set by the runtime), falling back to header extraction.\n *\n * @module ip-filter\n */\n\nimport { GatewayError } from \"../../core/errors\";\nimport type { PolicyInput, PolicyResult } from \"../../core/protocol\";\nimport { isInRange, type ParsedCIDR, parseCIDR } from \"../../utils/cidr\";\nimport { type ExtractClientIpOptions, extractClientIp } from \"../../utils/ip\";\nimport { Priority, withSkip } from \"../sdk\";\nimport type { Policy, PolicyConfig } from \"../types\";\n\nexport interface IpFilterConfig extends PolicyConfig {\n /** IPs or CIDR ranges to allow (allowlist mode). */\n allow?: string[];\n /** IPs or CIDR ranges to deny (denylist mode). */\n deny?: string[];\n /** Filter mode. Default: \"deny\". */\n mode?: \"allow\" | \"deny\";\n /** Ordered list of headers to inspect for the client IP. Default: `[\"cf-connecting-ip\", \"x-forwarded-for\"]`. */\n ipHeaders?: string[];\n}\n\n/**\n * Block or allow requests based on client IP address or CIDR range.\n *\n * Supports both allowlist and denylist modes. Client IP is extracted from\n * `CF-Connecting-IP` (Cloudflare) or `X-Forwarded-For`. Accepts individual\n * IPs (`192.168.1.1`) and CIDR notation (`10.0.0.0/8`).\n *\n * @param config - IP filter rules and mode selection.\n * @returns A {@link Policy} at priority 1 (runs before everything else).\n *\n * @example\n * ```ts\n * // Allow only internal IPs\n * ipFilter({ mode: \"allow\", allow: [\"10.0.0.0/8\", \"172.16.0.0/12\"] });\n *\n * // Block known bad actors\n * ipFilter({ deny: [\"203.0.113.0/24\", \"198.51.100.42\"] });\n * ```\n */\nexport function ipFilter(config: IpFilterConfig): Policy {\n const mode = config.mode ?? \"deny\";\n const ipHeaderOptions: ExtractClientIpOptions = config.ipHeaders\n ? { ipHeaders: config.ipHeaders }\n : {};\n const allowRanges: ParsedCIDR[] = (config.allow ?? [])\n .map(parseCIDR)\n .filter((r): r is ParsedCIDR => r !== null);\n const denyRanges: ParsedCIDR[] = (config.deny ?? [])\n .map(parseCIDR)\n .filter((r): r is ParsedCIDR => r !== null);\n\n // ── Shared logic - used by both handler and evaluate ────────────\n function checkIp(ip: string): PolicyResult {\n if (mode === \"allow\") {\n if (!isInRange(ip, allowRanges)) {\n return {\n action: \"reject\",\n status: 403,\n code: \"ip_denied\",\n message: \"Access denied\",\n };\n }\n } else {\n if (isInRange(ip, denyRanges)) {\n return {\n action: \"reject\",\n status: 403,\n code: \"ip_denied\",\n message: \"Access denied\",\n };\n }\n }\n return { action: \"continue\" };\n }\n\n // ── HTTP handler (Hono middleware) ──────────────────────────────\n const handler: import(\"hono\").MiddlewareHandler = async (c, next) => {\n const ip = extractClientIp(c.req.raw.headers, ipHeaderOptions);\n const result = checkIp(ip);\n if (result.action === \"reject\") {\n throw new GatewayError(result.status, result.code, result.message);\n }\n await next();\n };\n\n return {\n name: \"ip-filter\",\n priority: Priority.IP_FILTER,\n handler: withSkip(config.skip, handler),\n phases: [\"request-headers\"],\n // ── Protocol-agnostic evaluator ────────────────────────────────\n evaluate: {\n onRequest: async (input: PolicyInput) => {\n const ip =\n input.clientIp ?? extractClientIp(input.headers, ipHeaderOptions);\n return checkIp(ip);\n },\n },\n };\n}\n"],"mappings":"AAUA,SAAS,oBAAoB;AAE7B,SAAS,WAA4B,iBAAiB;AACtD,SAAsC,uBAAuB;AAC7D,SAAS,UAAU,gBAAgB;AAiC5B,SAAS,SAAS,QAAgC;AACvD,QAAM,OAAO,OAAO,QAAQ;AAC5B,QAAM,kBAA0C,OAAO,YACnD,EAAE,WAAW,OAAO,UAAU,IAC9B,CAAC;AACL,QAAM,eAA6B,OAAO,SAAS,CAAC,GACjD,IAAI,SAAS,EACb,OAAO,CAAC,MAAuB,MAAM,IAAI;AAC5C,QAAM,cAA4B,OAAO,QAAQ,CAAC,GAC/C,IAAI,SAAS,EACb,OAAO,CAAC,MAAuB,MAAM,IAAI;AAG5C,WAAS,QAAQ,IAA0B;AACzC,QAAI,SAAS,SAAS;AACpB,UAAI,CAAC,UAAU,IAAI,WAAW,GAAG;AAC/B,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF,OAAO;AACL,UAAI,UAAU,IAAI,UAAU,GAAG;AAC7B,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AACA,WAAO,EAAE,QAAQ,WAAW;AAAA,EAC9B;AAGA,QAAM,UAA4C,OAAO,GAAG,SAAS;AACnE,UAAM,KAAK,gBAAgB,EAAE,IAAI,IAAI,SAAS,eAAe;AAC7D,UAAM,SAAS,QAAQ,EAAE;AACzB,QAAI,OAAO,WAAW,UAAU;AAC9B,YAAM,IAAI,aAAa,OAAO,QAAQ,OAAO,MAAM,OAAO,OAAO;AAAA,IACnE;AACA,UAAM,KAAK;AAAA,EACb;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,SAAS;AAAA,IACnB,SAAS,SAAS,OAAO,MAAM,OAAO;AAAA,IACtC,QAAQ,CAAC,iBAAiB;AAAA;AAAA,IAE1B,UAAU;AAAA,MACR,WAAW,OAAO,UAAuB;AACvC,cAAM,KACJ,MAAM,YAAY,gBAAgB,MAAM,SAAS,eAAe;AAClE,eAAO,QAAQ,EAAE;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,51 @@
1
+ import { g as PolicyConfig, P as Policy } from '../../protocol-2fD3DJrL.js';
2
+ import 'hono';
3
+ import '../sdk/trace.js';
4
+ import '@vivero/stoma-core';
5
+
6
+ interface JsonThreatProtectionConfig extends PolicyConfig {
7
+ /** Maximum nesting depth. Default: `20`. */
8
+ maxDepth?: number;
9
+ /** Maximum number of keys per object. Default: `100`. */
10
+ maxKeys?: number;
11
+ /** Maximum string value length (also applies to object keys). Default: `10000`. */
12
+ maxStringLength?: number;
13
+ /** Maximum array length. Default: `100`. */
14
+ maxArraySize?: number;
15
+ /** Maximum raw body size in bytes. Checked BEFORE parsing. Default: `1048576` (1 MB). */
16
+ maxBodySize?: number;
17
+ /**
18
+ * Content types to inspect.
19
+ * Requests with other content types pass through without inspection.
20
+ * Default: `["application/json"]`.
21
+ */
22
+ contentTypes?: string[];
23
+ }
24
+ /**
25
+ * JSON threat protection policy.
26
+ *
27
+ * Enforces structural limits on JSON request bodies to prevent abuse
28
+ * from deeply nested objects, excessively large arrays, long strings,
29
+ * or oversized payloads. Runs at EARLY priority to reject malicious
30
+ * payloads before they reach business logic.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { jsonThreatProtection } from "@vivero/stoma";
35
+ *
36
+ * // Default limits (20 depth, 100 keys, 10K string, 100 array, 1MB body)
37
+ * jsonThreatProtection();
38
+ *
39
+ * // Strict limits for a public API
40
+ * jsonThreatProtection({
41
+ * maxDepth: 5,
42
+ * maxKeys: 20,
43
+ * maxStringLength: 1000,
44
+ * maxArraySize: 50,
45
+ * maxBodySize: 102400, // 100KB
46
+ * });
47
+ * ```
48
+ */
49
+ declare const jsonThreatProtection: (config?: JsonThreatProtectionConfig | undefined) => Policy;
50
+
51
+ export { type JsonThreatProtectionConfig, jsonThreatProtection };