node-fastify 5.8.3

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 (354) hide show
  1. package/.borp.yaml +3 -0
  2. package/.markdownlint-cli2.yaml +22 -0
  3. package/.prettierignore +1 -0
  4. package/GOVERNANCE.md +4 -0
  5. package/LICENSE +21 -0
  6. package/PROJECT_CHARTER.md +126 -0
  7. package/README.md +423 -0
  8. package/SECURITY.md +220 -0
  9. package/SPONSORS.md +24 -0
  10. package/build/build-error-serializer.js +35 -0
  11. package/build/build-validation.js +169 -0
  12. package/build/sync-version.js +11 -0
  13. package/docs/Guides/Benchmarking.md +60 -0
  14. package/docs/Guides/Database.md +321 -0
  15. package/docs/Guides/Delay-Accepting-Requests.md +608 -0
  16. package/docs/Guides/Detecting-When-Clients-Abort.md +172 -0
  17. package/docs/Guides/Ecosystem.md +726 -0
  18. package/docs/Guides/Fluent-Schema.md +127 -0
  19. package/docs/Guides/Getting-Started.md +620 -0
  20. package/docs/Guides/Index.md +43 -0
  21. package/docs/Guides/Migration-Guide-V3.md +287 -0
  22. package/docs/Guides/Migration-Guide-V4.md +267 -0
  23. package/docs/Guides/Migration-Guide-V5.md +727 -0
  24. package/docs/Guides/Plugins-Guide.md +520 -0
  25. package/docs/Guides/Prototype-Poisoning.md +383 -0
  26. package/docs/Guides/Recommendations.md +378 -0
  27. package/docs/Guides/Serverless.md +604 -0
  28. package/docs/Guides/Style-Guide.md +246 -0
  29. package/docs/Guides/Testing.md +481 -0
  30. package/docs/Guides/Write-Plugin.md +103 -0
  31. package/docs/Guides/Write-Type-Provider.md +34 -0
  32. package/docs/Reference/ContentTypeParser.md +271 -0
  33. package/docs/Reference/Decorators.md +436 -0
  34. package/docs/Reference/Encapsulation.md +194 -0
  35. package/docs/Reference/Errors.md +377 -0
  36. package/docs/Reference/HTTP2.md +94 -0
  37. package/docs/Reference/Hooks.md +958 -0
  38. package/docs/Reference/Index.md +73 -0
  39. package/docs/Reference/LTS.md +86 -0
  40. package/docs/Reference/Lifecycle.md +99 -0
  41. package/docs/Reference/Logging.md +268 -0
  42. package/docs/Reference/Middleware.md +79 -0
  43. package/docs/Reference/Plugins.md +245 -0
  44. package/docs/Reference/Principles.md +73 -0
  45. package/docs/Reference/Reply.md +1001 -0
  46. package/docs/Reference/Request.md +295 -0
  47. package/docs/Reference/Routes.md +802 -0
  48. package/docs/Reference/Server.md +2389 -0
  49. package/docs/Reference/Type-Providers.md +256 -0
  50. package/docs/Reference/TypeScript.md +1729 -0
  51. package/docs/Reference/Validation-and-Serialization.md +1130 -0
  52. package/docs/Reference/Warnings.md +58 -0
  53. package/docs/index.md +24 -0
  54. package/docs/resources/encapsulation_context.drawio +1 -0
  55. package/docs/resources/encapsulation_context.svg +3 -0
  56. package/eslint.config.js +35 -0
  57. package/examples/asyncawait.js +38 -0
  58. package/examples/benchmark/body.json +3 -0
  59. package/examples/benchmark/hooks-benchmark-async-await.js +44 -0
  60. package/examples/benchmark/hooks-benchmark.js +52 -0
  61. package/examples/benchmark/parser.js +47 -0
  62. package/examples/benchmark/simple.js +30 -0
  63. package/examples/benchmark/webstream.js +27 -0
  64. package/examples/hooks.js +91 -0
  65. package/examples/http2.js +39 -0
  66. package/examples/https.js +38 -0
  67. package/examples/parser.js +53 -0
  68. package/examples/plugin.js +12 -0
  69. package/examples/route-prefix.js +38 -0
  70. package/examples/shared-schema.js +38 -0
  71. package/examples/simple-stream.js +20 -0
  72. package/examples/simple.js +32 -0
  73. package/examples/simple.mjs +27 -0
  74. package/examples/typescript-server.ts +79 -0
  75. package/examples/use-plugin.js +29 -0
  76. package/fastify.d.ts +253 -0
  77. package/fastify.js +985 -0
  78. package/integration/server.js +29 -0
  79. package/integration/test.sh +23 -0
  80. package/lib/config-validator.js +1266 -0
  81. package/lib/content-type-parser.js +413 -0
  82. package/lib/content-type.js +160 -0
  83. package/lib/context.js +98 -0
  84. package/lib/decorate.js +152 -0
  85. package/lib/error-handler.js +173 -0
  86. package/lib/error-serializer.js +134 -0
  87. package/lib/error-status.js +14 -0
  88. package/lib/errors.js +516 -0
  89. package/lib/four-oh-four.js +190 -0
  90. package/lib/handle-request.js +195 -0
  91. package/lib/head-route.js +45 -0
  92. package/lib/hooks.js +429 -0
  93. package/lib/initial-config-validation.js +37 -0
  94. package/lib/logger-factory.js +136 -0
  95. package/lib/logger-pino.js +68 -0
  96. package/lib/noop-set.js +10 -0
  97. package/lib/plugin-override.js +90 -0
  98. package/lib/plugin-utils.js +169 -0
  99. package/lib/promise.js +23 -0
  100. package/lib/reply.js +1030 -0
  101. package/lib/req-id-gen-factory.js +52 -0
  102. package/lib/request.js +391 -0
  103. package/lib/route.js +686 -0
  104. package/lib/schema-controller.js +164 -0
  105. package/lib/schemas.js +207 -0
  106. package/lib/server.js +441 -0
  107. package/lib/symbols.js +71 -0
  108. package/lib/validation.js +280 -0
  109. package/lib/warnings.js +57 -0
  110. package/lib/wrap-thenable.js +84 -0
  111. package/package.json +225 -0
  112. package/scripts/validate-ecosystem-links.js +179 -0
  113. package/test/404s.test.js +2035 -0
  114. package/test/500s.test.js +422 -0
  115. package/test/allow-unsafe-regex.test.js +92 -0
  116. package/test/als.test.js +65 -0
  117. package/test/async-await.test.js +705 -0
  118. package/test/async-dispose.test.js +20 -0
  119. package/test/async_hooks.test.js +52 -0
  120. package/test/body-limit.test.js +224 -0
  121. package/test/buffer.test.js +74 -0
  122. package/test/build/error-serializer.test.js +36 -0
  123. package/test/build/version.test.js +14 -0
  124. package/test/build-certificate.js +109 -0
  125. package/test/bundler/README.md +29 -0
  126. package/test/bundler/esbuild/bundler-test.js +32 -0
  127. package/test/bundler/esbuild/package.json +10 -0
  128. package/test/bundler/esbuild/src/fail-plugin-version.js +14 -0
  129. package/test/bundler/esbuild/src/index.js +9 -0
  130. package/test/bundler/webpack/bundler-test.js +32 -0
  131. package/test/bundler/webpack/package.json +11 -0
  132. package/test/bundler/webpack/src/fail-plugin-version.js +14 -0
  133. package/test/bundler/webpack/src/index.js +9 -0
  134. package/test/bundler/webpack/webpack.config.js +15 -0
  135. package/test/case-insensitive.test.js +102 -0
  136. package/test/chainable.test.js +40 -0
  137. package/test/child-logger-factory.test.js +128 -0
  138. package/test/client-timeout.test.js +38 -0
  139. package/test/close-pipelining.test.js +78 -0
  140. package/test/close.test.js +706 -0
  141. package/test/conditional-pino.test.js +47 -0
  142. package/test/connection-timeout.test.js +42 -0
  143. package/test/constrained-routes.test.js +1138 -0
  144. package/test/content-length.test.js +174 -0
  145. package/test/content-parser.test.js +739 -0
  146. package/test/content-type.test.js +181 -0
  147. package/test/context-config.test.js +164 -0
  148. package/test/custom-http-server.test.js +118 -0
  149. package/test/custom-parser-async.test.js +59 -0
  150. package/test/custom-parser.0.test.js +701 -0
  151. package/test/custom-parser.1.test.js +266 -0
  152. package/test/custom-parser.2.test.js +91 -0
  153. package/test/custom-parser.3.test.js +208 -0
  154. package/test/custom-parser.4.test.js +218 -0
  155. package/test/custom-parser.5.test.js +130 -0
  156. package/test/custom-querystring-parser.test.js +129 -0
  157. package/test/decorator.test.js +1330 -0
  158. package/test/delete.test.js +344 -0
  159. package/test/diagnostics-channel/404.test.js +49 -0
  160. package/test/diagnostics-channel/async-delay-request.test.js +65 -0
  161. package/test/diagnostics-channel/async-request.test.js +64 -0
  162. package/test/diagnostics-channel/error-before-handler.test.js +35 -0
  163. package/test/diagnostics-channel/error-request.test.js +53 -0
  164. package/test/diagnostics-channel/error-status.test.js +123 -0
  165. package/test/diagnostics-channel/init.test.js +50 -0
  166. package/test/diagnostics-channel/sync-delay-request.test.js +49 -0
  167. package/test/diagnostics-channel/sync-request-reply.test.js +51 -0
  168. package/test/diagnostics-channel/sync-request.test.js +54 -0
  169. package/test/encapsulated-child-logger-factory.test.js +69 -0
  170. package/test/encapsulated-error-handler.test.js +237 -0
  171. package/test/esm/errorCodes.test.mjs +10 -0
  172. package/test/esm/esm.test.mjs +13 -0
  173. package/test/esm/index.test.js +8 -0
  174. package/test/esm/named-exports.mjs +14 -0
  175. package/test/esm/other.mjs +8 -0
  176. package/test/esm/plugin.mjs +8 -0
  177. package/test/fastify-instance.test.js +300 -0
  178. package/test/find-route.test.js +152 -0
  179. package/test/fluent-schema.test.js +209 -0
  180. package/test/genReqId.test.js +426 -0
  181. package/test/handler-context.test.js +45 -0
  182. package/test/handler-timeout.test.js +367 -0
  183. package/test/has-route.test.js +88 -0
  184. package/test/header-overflow.test.js +55 -0
  185. package/test/helper.js +496 -0
  186. package/test/hooks-async.test.js +1099 -0
  187. package/test/hooks.on-listen.test.js +1162 -0
  188. package/test/hooks.on-ready.test.js +421 -0
  189. package/test/hooks.test.js +3578 -0
  190. package/test/http-methods/copy.test.js +35 -0
  191. package/test/http-methods/custom-http-methods.test.js +114 -0
  192. package/test/http-methods/get.test.js +412 -0
  193. package/test/http-methods/head.test.js +263 -0
  194. package/test/http-methods/lock.test.js +108 -0
  195. package/test/http-methods/mkcalendar.test.js +143 -0
  196. package/test/http-methods/mkcol.test.js +35 -0
  197. package/test/http-methods/move.test.js +42 -0
  198. package/test/http-methods/propfind.test.js +136 -0
  199. package/test/http-methods/proppatch.test.js +105 -0
  200. package/test/http-methods/report.test.js +142 -0
  201. package/test/http-methods/search.test.js +233 -0
  202. package/test/http-methods/trace.test.js +21 -0
  203. package/test/http-methods/unlock.test.js +38 -0
  204. package/test/http2/closing.test.js +270 -0
  205. package/test/http2/constraint.test.js +109 -0
  206. package/test/http2/head.test.js +34 -0
  207. package/test/http2/plain.test.js +68 -0
  208. package/test/http2/secure-with-fallback.test.js +113 -0
  209. package/test/http2/secure.test.js +67 -0
  210. package/test/http2/unknown-http-method.test.js +34 -0
  211. package/test/https/custom-https-server.test.js +58 -0
  212. package/test/https/https.test.js +136 -0
  213. package/test/imports.test.js +17 -0
  214. package/test/inject.test.js +502 -0
  215. package/test/input-validation.js +335 -0
  216. package/test/internals/all.test.js +38 -0
  217. package/test/internals/content-type-parser.test.js +111 -0
  218. package/test/internals/context.test.js +31 -0
  219. package/test/internals/decorator.test.js +156 -0
  220. package/test/internals/errors.test.js +982 -0
  221. package/test/internals/handle-request.test.js +270 -0
  222. package/test/internals/hook-runner.test.js +449 -0
  223. package/test/internals/hooks.test.js +96 -0
  224. package/test/internals/initial-config.test.js +383 -0
  225. package/test/internals/logger.test.js +163 -0
  226. package/test/internals/plugin.test.js +170 -0
  227. package/test/internals/promise.test.js +63 -0
  228. package/test/internals/reply-serialize.test.js +714 -0
  229. package/test/internals/reply.test.js +1920 -0
  230. package/test/internals/req-id-gen-factory.test.js +133 -0
  231. package/test/internals/request-validate.test.js +1402 -0
  232. package/test/internals/request.test.js +506 -0
  233. package/test/internals/schema-controller-perf.test.js +40 -0
  234. package/test/internals/server.test.js +91 -0
  235. package/test/internals/validation.test.js +352 -0
  236. package/test/issue-4959.test.js +118 -0
  237. package/test/keep-alive-timeout.test.js +42 -0
  238. package/test/listen.1.test.js +154 -0
  239. package/test/listen.2.test.js +113 -0
  240. package/test/listen.3.test.js +83 -0
  241. package/test/listen.4.test.js +168 -0
  242. package/test/listen.5.test.js +122 -0
  243. package/test/logger/instantiation.test.js +341 -0
  244. package/test/logger/logger-test-utils.js +47 -0
  245. package/test/logger/logging.test.js +460 -0
  246. package/test/logger/options.test.js +579 -0
  247. package/test/logger/request.test.js +292 -0
  248. package/test/logger/response.test.js +183 -0
  249. package/test/logger/tap-parallel-not-ok +0 -0
  250. package/test/max-requests-per-socket.test.js +113 -0
  251. package/test/middleware.test.js +37 -0
  252. package/test/noop-set.test.js +19 -0
  253. package/test/nullable-validation.test.js +187 -0
  254. package/test/options.error-handler.test.js +5 -0
  255. package/test/options.test.js +5 -0
  256. package/test/output-validation.test.js +140 -0
  257. package/test/patch.error-handler.test.js +5 -0
  258. package/test/patch.test.js +5 -0
  259. package/test/plugin.1.test.js +230 -0
  260. package/test/plugin.2.test.js +314 -0
  261. package/test/plugin.3.test.js +287 -0
  262. package/test/plugin.4.test.js +504 -0
  263. package/test/plugin.helper.js +8 -0
  264. package/test/plugin.name.display.js +10 -0
  265. package/test/post-empty-body.test.js +38 -0
  266. package/test/pretty-print.test.js +366 -0
  267. package/test/promises.test.js +125 -0
  268. package/test/proto-poisoning.test.js +145 -0
  269. package/test/put.error-handler.test.js +5 -0
  270. package/test/put.test.js +5 -0
  271. package/test/register.test.js +184 -0
  272. package/test/reply-code.test.js +148 -0
  273. package/test/reply-early-hints.test.js +100 -0
  274. package/test/reply-error.test.js +815 -0
  275. package/test/reply-trailers.test.js +445 -0
  276. package/test/reply-web-stream-locked.test.js +37 -0
  277. package/test/request-error.test.js +624 -0
  278. package/test/request-header-host.test.js +339 -0
  279. package/test/request-id.test.js +118 -0
  280. package/test/request-timeout.test.js +53 -0
  281. package/test/route-hooks.test.js +635 -0
  282. package/test/route-prefix.test.js +904 -0
  283. package/test/route-shorthand.test.js +48 -0
  284. package/test/route.1.test.js +259 -0
  285. package/test/route.2.test.js +100 -0
  286. package/test/route.3.test.js +213 -0
  287. package/test/route.4.test.js +127 -0
  288. package/test/route.5.test.js +211 -0
  289. package/test/route.6.test.js +306 -0
  290. package/test/route.7.test.js +406 -0
  291. package/test/route.8.test.js +225 -0
  292. package/test/router-options.test.js +1108 -0
  293. package/test/same-shape.test.js +124 -0
  294. package/test/schema-examples.test.js +661 -0
  295. package/test/schema-feature.test.js +2198 -0
  296. package/test/schema-serialization.test.js +1171 -0
  297. package/test/schema-special-usage.test.js +1348 -0
  298. package/test/schema-validation.test.js +1572 -0
  299. package/test/scripts/validate-ecosystem-links.test.js +339 -0
  300. package/test/serialize-response.test.js +186 -0
  301. package/test/server.test.js +347 -0
  302. package/test/set-error-handler.test.js +69 -0
  303. package/test/skip-reply-send.test.js +317 -0
  304. package/test/stream-serializers.test.js +40 -0
  305. package/test/stream.1.test.js +94 -0
  306. package/test/stream.2.test.js +129 -0
  307. package/test/stream.3.test.js +198 -0
  308. package/test/stream.4.test.js +176 -0
  309. package/test/stream.5.test.js +188 -0
  310. package/test/sync-routes.test.js +32 -0
  311. package/test/throw.test.js +359 -0
  312. package/test/toolkit.js +63 -0
  313. package/test/trust-proxy.test.js +162 -0
  314. package/test/type-provider.test.js +22 -0
  315. package/test/types/content-type-parser.test-d.ts +72 -0
  316. package/test/types/decorate-request-reply.test-d.ts +18 -0
  317. package/test/types/dummy-plugin.ts +9 -0
  318. package/test/types/errors.test-d.ts +90 -0
  319. package/test/types/fastify.test-d.ts +352 -0
  320. package/test/types/hooks.test-d.ts +550 -0
  321. package/test/types/import.ts +2 -0
  322. package/test/types/instance.test-d.ts +588 -0
  323. package/test/types/logger.test-d.ts +277 -0
  324. package/test/types/plugin.test-d.ts +97 -0
  325. package/test/types/register.test-d.ts +237 -0
  326. package/test/types/reply.test-d.ts +254 -0
  327. package/test/types/request.test-d.ts +188 -0
  328. package/test/types/route.test-d.ts +553 -0
  329. package/test/types/schema.test-d.ts +135 -0
  330. package/test/types/serverFactory.test-d.ts +37 -0
  331. package/test/types/type-provider.test-d.ts +1213 -0
  332. package/test/types/using.test-d.ts +17 -0
  333. package/test/upgrade.test.js +52 -0
  334. package/test/url-rewriting.test.js +122 -0
  335. package/test/use-semicolon-delimiter.test.js +168 -0
  336. package/test/validation-error-handling.test.js +900 -0
  337. package/test/versioned-routes.test.js +603 -0
  338. package/test/web-api.test.js +616 -0
  339. package/test/wrap-thenable.test.js +30 -0
  340. package/types/content-type-parser.d.ts +75 -0
  341. package/types/context.d.ts +22 -0
  342. package/types/errors.d.ts +92 -0
  343. package/types/hooks.d.ts +875 -0
  344. package/types/instance.d.ts +609 -0
  345. package/types/logger.d.ts +107 -0
  346. package/types/plugin.d.ts +44 -0
  347. package/types/register.d.ts +42 -0
  348. package/types/reply.d.ts +81 -0
  349. package/types/request.d.ts +95 -0
  350. package/types/route.d.ts +199 -0
  351. package/types/schema.d.ts +61 -0
  352. package/types/server-factory.d.ts +19 -0
  353. package/types/type-provider.d.ts +130 -0
  354. package/types/utils.d.ts +98 -0
@@ -0,0 +1,383 @@
1
+ > The following is an article written by Eran Hammer.
2
+ > It is reproduced here for posterity [with permission](https://github.com/fastify/fastify/issues/1426#issuecomment-817957913).
3
+ > It has been reformatted from the original HTML source to Markdown source,
4
+ > but otherwise remains the same. The original HTML can be retrieved from the
5
+ > above permission link.
6
+
7
+ ## History behind prototype poisoning
8
+ <a id="pp"></a>
9
+
10
+ Based on the article by Eran Hammer,the issue is created by a web security bug.
11
+ It is also a perfect illustration of the efforts required to maintain
12
+ open-source software and the limitations of existing communication channels.
13
+
14
+ But first, if we use a JavaScript framework to process incoming JSON data, take
15
+ a moment to read up on [Prototype Poisoning](https://medium.com/intrinsic/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96)
16
+ in general, and the specific
17
+ [technical details](https://github.com/hapijs/hapi/issues/3916) of this issue.
18
+ This could be a critical issue so, we might need to verify your own code first.
19
+ It focuses on specific framework however, any solution that uses `JSON.parse()`
20
+ to process external data is potentially at risk.
21
+
22
+ ### BOOM
23
+ <a id="pp-boom"></a>
24
+
25
+ The engineering team at Lob (long time generous supporters of my work!) reported
26
+ a critical security vulnerability they identified in our data validation
27
+ module — [joi](https://github.com/hapijs/joi). They provided some technical
28
+ details and a proposed solution.
29
+
30
+ The main purpose of a data validation library is to ensure the output fully
31
+ complies with the rules defined. If it doesn't, validation fails. If it passes,
32
+ we can blindly trust that the data you are working with is safe. In fact, most
33
+ developers treat validated input as completely safe from a system integrity
34
+ perspective which is crucial!
35
+
36
+ In our case, the Lob team provided an example where some data was able to escape
37
+ by the validation logic and pass through undetected. This is the worst possible
38
+ defect a validation library can have.
39
+
40
+ ### Prototype in a nutshell
41
+ <a id="pp-nutshell"></a>
42
+
43
+ To understand this, we need to understand how JavaScript works a bit.
44
+ Every object in JavaScript can have a prototype. It is a set of methods and
45
+ properties it "inherits" from another object. I have put inherits in quotes
46
+ because JavaScript isn't really an object-oriented language. It is a prototype-
47
+ based object-oriented language.
48
+
49
+ A long time ago, for a bunch of irrelevant reasons, someone decided that it
50
+ would be a good idea to use the special property name `__proto__` to access (and
51
+ set) an object's prototype. This has since been deprecated but nevertheless,
52
+ fully supported.
53
+
54
+ To demonstrate:
55
+
56
+ ```
57
+ > const a = { b: 5 };
58
+ > a.b;
59
+ 5
60
+ > a.__proto__ = { c: 6 };
61
+ > a.c;
62
+ 6
63
+ > a;
64
+ { b: 5 }
65
+ ```
66
+
67
+ The object doesn't have a `c` property, but its prototype does.
68
+ When validating the object, the validation library ignores the prototype and
69
+ only validates the object's own properties. This allows `c` to sneak in via the
70
+ prototype.
71
+
72
+ Another important part is the way `JSON.parse()` — a utility
73
+ provided by the language to convert JSON formatted text into
74
+ objects  —  handles this magic `__proto__` property name.
75
+
76
+ ```
77
+ > const text = '{"b": 5, "__proto__": { "c": 6 }}';
78
+ > const a = JSON.parse(text);
79
+ > a;
80
+ {b: 5, __proto__: { c: 6 }}
81
+ ```
82
+ Notice how `a` has a `__proto__` property. This is not a prototype reference. It
83
+ is a simple object property key, just like `b`. As we've seen from the first
84
+ example, we can't actually create this key through assignment as that invokes
85
+ the prototype magic and sets an actual prototype. `JSON.parse()` however, sets a
86
+ simple property with that poisonous name.
87
+
88
+ By itself, the object created by `JSON.parse()` is perfectly safe. It doesn't
89
+ have a prototype of its own. It has a seemingly harmless property that just
90
+ happens to overlap with a built-in JavaScript magic name.
91
+
92
+ However, other methods are not as lucky:
93
+
94
+ ```
95
+ > const x = Object.assign({}, a);
96
+ > x;
97
+ { b: 5}
98
+ > x.c;
99
+ 6;
100
+ ```
101
+
102
+ If we take the `a` object created earlier by `JSON.parse()` and pass it to the
103
+ helpful `Object.assign()` method (used to perform a shallow copy of all the top
104
+ level properties of `a` into the provided empty `{}` object), the magic
105
+ `__proto__` property "leaks" and becomes `x` 's actual prototype.
106
+
107
+ Surprise!
108
+
109
+ If you get some external text input and parse it with `JSON.parse()`
110
+ then perform some simple manipulation of that object (e.g shallow clone and add
111
+ an `id` ), and pass it to our validation library, it would sneak in undetected
112
+ via `__proto__`.
113
+
114
+ ### Oh joi!
115
+ <a id="pp-oh-joi"></a>
116
+
117
+ The first question is, of course, why does the validation module **joi** ignore
118
+ the prototype and let potentially harmful data through? We asked ourselves the
119
+ same question and our instant thought was "it was an oversight". A bug - a really
120
+ big mistake. The joi module should not have allowed this to happen. But…
121
+
122
+ While joi is used primarily for validating web input data, it also has a
123
+ significant user base using it to validate internal objects, some of which have
124
+ prototypes. The fact that joi ignores the prototype is a helpful "feature". It
125
+ allows validating the object's own properties while ignoring what could be a
126
+ very complicated prototype structure (with many methods and literal properties).
127
+
128
+ Any solution at the joi level would mean breaking some currently working code.
129
+
130
+ ### The right thing
131
+ <a id="pp-right-thing"></a>
132
+
133
+ At this point, we were looking at a devastatingly bad security vulnerability.
134
+ Right up there in the upper echelons of epic security failures. All we knew is
135
+ that our extremely popular data validation library fails to block harmful data,
136
+ and that this data is trivial to sneak through. All you need to do is add
137
+ `__proto__` and some crap to a JSON input and send it on its way to an
138
+ application built using our tools.
139
+
140
+ (Dramatic pause)
141
+
142
+ We knew we had to fix joi to prevent this but given the scale of this issue, we
143
+ had to do it in a way that will put a fix out without drawing too much attention
144
+ to it — without making it too easy to exploit — at least for a few days until
145
+ most systems received the update.
146
+
147
+ Sneaking a fix isn't the hardest thing to accomplish. If you combine it with an
148
+ otherwise purposeless refactor of the code, and throw in a few unrelated bug
149
+ fixes and maybe a cool new feature, you can publish a new version without
150
+ drawing attention to the real issue being fixed.
151
+
152
+ The problem was, the right fix was going to break valid use cases. You see, joi
153
+ has no way of knowing if you want it to ignore the prototype you set, or block
154
+ the prototype set by an attacker. A solution that fixes the exploit will break
155
+ code and breaking code tends to get a lot of attention.
156
+
157
+ On the other hand, if we released a proper ([semantically
158
+ versioned](https://semver.org/)) fix, mark it as a breaking change, and add a
159
+ new API to explicitly tell joi what you want it to do with the prototype, we
160
+ will share with the world how to exploit this vulnerability while also making it
161
+ more time consuming for systems to upgrade (breaking changes never get applied
162
+ automatically by build tools).
163
+
164
+
165
+ ### A detour
166
+ <a id="pp-detour"></a>
167
+
168
+ While the issue at hand was about incoming request payloads, we had to pause and
169
+ check if it could also impact data coming via the query string, cookies, and
170
+ headers. Basically, anything that gets serialized into objects from text.
171
+
172
+ We quickly confirmed node default query string parser was fine as well as its
173
+ header parser. I identified one potential issue with base64-encoded JSON cookies
174
+ as well as the usage of custom query string parsers. We also wrote some tests to
175
+ confirm that the most popular third-party query string parser  —
176
+ [qs](https://www.npmjs.com/package/qs) —  was not vulnerable (it is not!).
177
+
178
+ ### A development
179
+ <a id="pp-a-development"></a>
180
+
181
+ Throughout this triage, we just assumed that the offending input with its
182
+ poisoned prototype was coming into joi from hapi, the web framework connecting
183
+ the hapi.js ecosystem. Further investigation by the Lob team found that the
184
+ problem was a bit more nuanced.
185
+
186
+ hapi used `JSON.parse()` to process incoming data. It first set the result
187
+ object as a `payload` property of the incoming request, and then passed that
188
+ same object for validation by joi before being passed to the application
189
+ business logic for processing. Since `JSON.parse()` doesn't actually leak the
190
+ `__proto__` property, it would arrive to joi with an invalid key and fail
191
+ validation.
192
+
193
+ However, hapi provides two extension points where the payload data can be
194
+ inspected (and processed) prior to validation. It is all properly documented and
195
+ well understood by most developers. The extension points are there to allow you
196
+ to interact with the raw inputs prior to validation for legitimate (and often
197
+ security related) reasons.
198
+
199
+ If during one of these two extension points, a developer used `Object.assign()`
200
+ or a similar method on the payload, the `__proto__` property would leak and
201
+ become an actual prototype.
202
+
203
+ ### Sigh of relief
204
+ <a id="pp-sigh-of-relief"></a>
205
+
206
+ We were now dealing with a much different level of awfulness. Manipulating the
207
+ payload object prior to validation is not common which meant this was no longer
208
+ a doomsday scenario. It was still potentially catastrophic but the exposure
209
+ dropped from every joi user to some very specific implementations.
210
+
211
+ We were no longer looking at a secretive joi release. The issue in joi is still
212
+ there, but we can now address it properly with a new API and breaking release
213
+ over the next few weeks.
214
+
215
+ We also knew that we can easily mitigate this vulnerability at the framework
216
+ level since it knows which data is coming from the outside and which is
217
+ internally generated. The framework is really the only piece that can protect
218
+ developers against making such unexpected mistakes.
219
+
220
+ ### Good news, bad news, no news?
221
+ <a id="pp-good-news-no-news"></a>
222
+
223
+ The good news was that this wasn't our fault. It wasn't a bug in hapi or joi. It
224
+ was only possible through a complex combination of actions that was not unique
225
+ to hapi or joi. This can happen with every other JavaScript framework. If hapi
226
+ is broken, then the world is broken.
227
+
228
+ Great — we solved the blame game.
229
+
230
+ The bad news is that when there is nothing to blame (other than JavaScript
231
+ itself), it is much harder getting it fixed.
232
+
233
+ The first question people ask once a security issue is found is if there is
234
+ going to be a CVE published. A CVE — Common Vulnerabilities and Exposures — is a
235
+ [database](https://cve.mitre.org/) of known security issues. It is a critical
236
+ component of web security. The benefit of publishing a CVE is that it
237
+ immediately triggers alarms and informs and often breaks automated builds until
238
+ the issue is resolved.
239
+
240
+ But what do we pin this to?
241
+
242
+ Probably, nothing. We are still debating whether we should tag some versions of
243
+ hapi with a warning. The "we" is the node security process. Since we now have a
244
+ new version of hapi that mitigate the problem by default, it can be considered a
245
+ fix. But because the fix isn't to a problem in hapi itself, it is not exactly
246
+ kosher to declare older versions harmful.
247
+
248
+ Publishing an advisory on previous versions of hapi for the sole purpose of
249
+ nudging people into awareness and upgrade is an abuse of the advisory process.
250
+ I'm personally fine with abusing it for the purpose of improving security but
251
+ that's not my call. As of this writing, it is still being debated.
252
+
253
+ ### The solution business
254
+ <a id="pp-solution-business"></a>
255
+
256
+ Mitigating the issue wasn't hard. Making it scale and safe was a bit more
257
+ involved. Since we knew where harmful data can enter the system, and we knew
258
+ where we used the problematic `JSON.parse()` we could replace it with a safe
259
+ implementation.
260
+
261
+ One problem. Validating data can be costly and we are now planning on validating
262
+ every incoming JSON text. The built-in `JSON.parse()` implementation is fast.
263
+ Really really fast. It is unlikely we can build a replacement that will be more
264
+ secure and anywhere as fast. Especially not overnight and without introducing
265
+ new bugs.
266
+
267
+ It was obvious we were going to wrap the existing `JSON.parse()` method with
268
+ some additional logic. We just had to make sure it was not adding too much
269
+ overhead. This isn't just a performance consideration but also a security one.
270
+ If we make it easy to slow down a system by simply sending specific data, we
271
+ make it easy to execute a [DoS
272
+ attack](https://en.wikipedia.org/wiki/Denial-of-service_attack) at very low
273
+ cost.
274
+
275
+ I came up with a stupidly simple solution: first parse the text using the
276
+ existing tools. If this didn't fail, scan the original raw text for the
277
+ offending string "__proto__". Only if we find it, perform an actual scan of the
278
+ object. We can't block every reference to "__proto__" — sometimes it is
279
+ perfectly valid value (like when writing about it here and sending this text
280
+ over to Medium for publication).
281
+
282
+ This made the "happy path" practically as fast as before. It just added one
283
+ function call, a quick text scan (again, very fast built-in implementation), and
284
+ a conditional return. The solution had negligible impact on the vast majority of
285
+ data expected to pass through it.
286
+
287
+ Next problem. The prototype property doesn't have to be at the top level of the
288
+ incoming object. It can be nested deep inside. This means we cannot just check
289
+ for the presence of it at the top level. We need to recursively iterate through
290
+ the object.
291
+
292
+ While recursive functions are a favorite tool, they could be disastrous when
293
+ writing security-conscious code. You see, recursive function increase the size
294
+ of the runtime call stack. The more times you loop, the longer the call stack
295
+ gets. At some point — KABOOM— you reach the maximum length and the process dies.
296
+
297
+ If you cannot guarantee the shape of the incoming data, recursive iteration
298
+ becomes an open threat. An attacker only needs to craft a deep enough object to
299
+ crash your servers.
300
+
301
+ I used a flat loop implementation that is both more memory efficient (less
302
+ function calls, less passing of temporary arguments) and more secure. I am not
303
+ pointing this out to brag, but to highlight how basic engineering practices can
304
+ create (or avoid) security pitfalls.
305
+
306
+ ### Putting it to the test
307
+ <a id="pp-putting-to-test"></a>
308
+
309
+ I sent the code to two people. First to [Nathan
310
+ LaFreniere](https://github.com/nlf) to double check the security properties of
311
+ the solution, and then to [Matteo Collina](https://github.com/mcollina) to
312
+ review the performance. They are among the very best at what they do and often
313
+ my go-to people.
314
+
315
+ The performance benchmarks confirmed that the "happy path" was practically
316
+ unaffected. The interesting findings was that removing the offending values was
317
+ faster then throwing an exception. This raised the question of what should be
318
+ the default behavior of the new module — which I called
319
+ [**bourne**](https://github.com/hapijs/bourne) —  error or sanitize.
320
+
321
+ The concern, again, was exposing the application to a DoS attack. If sending a
322
+ request with `__proto__` makes things 500% slower, that could be an easy vector
323
+ to exploit. But after a bit more testing we confirmed that sending **any**
324
+ invalid JSON text was creating a very similar cost.
325
+
326
+ In other words, if you parse JSON, invalid values are going to cost you more,
327
+ regardless of what makes them invalid. It is also important to remember that
328
+ while the benchmark showed the significant % cost of scanning suspected objects,
329
+ the actual cost in CPU time was still in the fraction of milliseconds. Important
330
+ to note and measure but not actually harmful.
331
+
332
+ ### hapi ever-after
333
+ <a id="pp-hapi-ever-after"></a>
334
+
335
+ There are a bunch of things to be grateful for.
336
+
337
+ The initial disclosure by the Lob team was perfect. It was reported privately,
338
+ to the right people, with the right information. They followed up with
339
+ additional findings, and gave us the time and space to resolve it the right way.
340
+ Lob also was a major sponsor of my work on hapi over the years and that
341
+ financial support is critical to allow everything else to happen. More on that
342
+ in a bit.
343
+
344
+ Triage was stressful but staffed with the right people. Having folks like
345
+ [Nicolas Morel](https://github.com/Marsup), Nathan, and Matteo, available and
346
+ eager to help is critical. This isn't easy to deal with without the pressure,
347
+ but with it, mistakes are likely without proper team collaboration.
348
+
349
+ We got lucky with the actual vulnerability. What started up looking like a
350
+ catastrophic problem, ended up being a delicate but straight-forward problem to
351
+ address.
352
+
353
+ We also got lucky by having full access to mitigate it at the source — didn't
354
+ need to send emails to some unknown framework maintainer and hope for a quick
355
+ answer. hapi's total control over all of its dependencies proved its usefulness
356
+ and security again. Not using [hapi](https://hapi.dev)? [Maybe you
357
+ should](https://hueniverse.com/why-you-should-consider-hapi-6163689bd7c2).
358
+
359
+ ### The after in happy ever-after
360
+ <a id="pp-after-ever-after"></a>
361
+
362
+ This is where I have to take advantage of this incident to reiterate the cost
363
+ and need for sustainable and secure open source.
364
+
365
+ My time alone on this one issue exceeded 20 hours. That's half a working week.
366
+ It came at the end of a month were I already spent over 30 hours publishing a
367
+ new major release of hapi (most of the work was done in December). This puts me
368
+ at a personal financial loss of over $5000 this month (I had to cut back on paid
369
+ client work to make time for it).
370
+
371
+ If you rely on code I maintain, this is exactly the level of support, quality,
372
+ and commitment you want (and lets be honest — expect). Most of you take it for
373
+ granted — not just my work but the work of hundreds of other dedicated open
374
+ source maintainers.
375
+
376
+ Because this work is important, I decided to try and make it not just
377
+ financially sustainable but to grow and expand it. There is so much to improve.
378
+ This is exactly what motivates me to implement the new [commercial licensing
379
+ plan](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-licensing-a-preview-f982662ee898)
380
+ coming in March. You can read more about it
381
+ [here](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-licensing-a-preview-f982662ee898).
382
+
383
+