fastify 3.25.3 → 3.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/build/build-validation.js +2 -0
- package/docs/Guides/Ecosystem.md +2 -1
- package/docs/Guides/Index.md +2 -0
- package/docs/Guides/Prototype-Poisoning.md +391 -0
- package/docs/Guides/Recommendations.md +1 -1
- package/docs/Reference/ContentTypeParser.md +4 -1
- package/docs/Reference/Plugins.md +4 -4
- package/docs/Reference/Request.md +3 -0
- package/docs/Reference/Server.md +52 -2
- package/docs/Reference/TypeScript.md +1 -1
- package/docs/Reference/Validation-and-Serialization.md +4 -1
- package/fastify.d.ts +3 -1
- package/fastify.js +41 -21
- package/lib/decorate.js +2 -2
- package/lib/errors.js +6 -1
- package/lib/noop-set.js +10 -0
- package/lib/pluginUtils.js +5 -0
- package/lib/reply.js +21 -11
- package/lib/route.js +34 -1
- package/lib/schema-controller.js +1 -1
- package/lib/server.js +1 -1
- package/lib/symbols.js +3 -1
- package/package.json +16 -17
- package/test/404s.test.js +25 -1
- package/test/async-await.test.js +3 -3
- package/test/bundler/esbuild/bundler-test.js +31 -0
- package/test/bundler/esbuild/package.json +10 -0
- package/test/bundler/esbuild/src/fail-plugin-version.js +12 -0
- package/test/bundler/esbuild/src/index.js +7 -0
- package/test/bundler/webpack/bundler-test.js +15 -4
- package/test/bundler/webpack/src/fail-plugin-version.js +1 -3
- package/test/bundler/webpack/src/index.js +1 -3
- package/test/close.test.js +39 -1
- package/test/context-config.test.js +4 -4
- package/test/custom-parser.test.js +30 -31
- package/test/inject.test.js +1 -1
- package/test/internals/all.test.js +2 -2
- package/test/internals/contentTypeParser.test.js +4 -4
- package/test/internals/handleRequest.test.js +8 -8
- package/test/internals/logger.test.js +1 -1
- package/test/logger.test.js +18 -18
- package/test/maxRequestsPerSocket.test.js +2 -2
- package/test/noop-set.test.js +19 -0
- package/test/plugin.name.display.js +10 -0
- package/test/plugin.test.js +18 -0
- package/test/route.test.js +12 -0
- package/test/schema-serialization.test.js +41 -0
- package/test/skip-reply-send.test.js +7 -7
- package/test/trust-proxy.test.js +1 -1
- package/test/types/fastify.test-d.ts +18 -0
- package/test/types/hooks.test-d.ts +34 -6
- package/test/types/instance.test-d.ts +26 -1
- package/test/validation-error-handling.test.js +1 -1
- package/test/versioned-routes.test.js +28 -4
- package/types/.eslintrc.json +1 -1
- package/types/hooks.d.ts +24 -20
- package/types/instance.d.ts +13 -1
- package/types/register.d.ts +1 -1
- package/types/schema.d.ts +14 -0
package/LICENSE
CHANGED
|
@@ -15,6 +15,7 @@ const ajv = new Ajv({
|
|
|
15
15
|
const defaultInitOptions = {
|
|
16
16
|
connectionTimeout: 0, // 0 sec
|
|
17
17
|
keepAliveTimeout: 5000, // 5 sec
|
|
18
|
+
forceCloseConnections: false, // keep-alive connections
|
|
18
19
|
maxRequestsPerSocket: 0, // no limit
|
|
19
20
|
requestTimeout: 0, // no limit
|
|
20
21
|
bodyLimit: 1024 * 1024, // 1 MiB
|
|
@@ -49,6 +50,7 @@ const schema = {
|
|
|
49
50
|
properties: {
|
|
50
51
|
connectionTimeout: { type: 'integer', default: defaultInitOptions.connectionTimeout },
|
|
51
52
|
keepAliveTimeout: { type: 'integer', default: defaultInitOptions.keepAliveTimeout },
|
|
53
|
+
forceCloseConnections: { type: 'boolean', default: defaultInitOptions.forceCloseConnections },
|
|
52
54
|
maxRequestsPerSocket: { type: 'integer', default: defaultInitOptions.maxRequestsPerSocket, nullable: true },
|
|
53
55
|
requestTimeout: { type: 'integer', default: defaultInitOptions.requestTimeout },
|
|
54
56
|
bodyLimit: { type: 'integer', default: defaultInitOptions.bodyLimit },
|
package/docs/Guides/Ecosystem.md
CHANGED
|
@@ -59,7 +59,7 @@ section.
|
|
|
59
59
|
- [`fastify-http-proxy`](https://github.com/fastify/fastify-http-proxy) Proxy
|
|
60
60
|
your HTTP requests to another server, with hooks.
|
|
61
61
|
- [`fastify-jwt`](https://github.com/fastify/fastify-jwt) JWT utils for Fastify,
|
|
62
|
-
internally uses [
|
|
62
|
+
internally uses [fast-jwt](https://github.com/nearform/fast-jwt).
|
|
63
63
|
- [`fastify-leveldb`](https://github.com/fastify/fastify-leveldb) Plugin to
|
|
64
64
|
share a common LevelDB connection across Fastify.
|
|
65
65
|
- [`fastify-mongodb`](https://github.com/fastify/fastify-mongodb) Fastify
|
|
@@ -130,6 +130,7 @@ section.
|
|
|
130
130
|
- [`@immobiliarelabs/fastify-metrics`](https://github.com/immobiliare/fastify-metrics)
|
|
131
131
|
Minimalistic and opinionated plugin that collects usage/process metrics and
|
|
132
132
|
dispatches to [statsd](https://github.com/statsd/statsd).
|
|
133
|
+
- [`@immobiliarelabs/fastify-sentry`](https://github.com/immobiliare/fastify-sentry) Sentry errors handler that just works! Install, add your DSN and you're good to go!
|
|
133
134
|
- [`@mgcrea/fastify-graceful-exit`](https://github.com/mgcrea/fastify-graceful-exit)
|
|
134
135
|
A plugin to close the server gracefully
|
|
135
136
|
- [`@mgcrea/fastify-request-logger`](https://github.com/mgcrea/fastify-request-logger)
|
package/docs/Guides/Index.md
CHANGED
|
@@ -20,6 +20,8 @@ This table of contents is in alphabetical order.
|
|
|
20
20
|
Fastify v3 from earlier versions.
|
|
21
21
|
+ [Plugins Guide](./Plugins-Guide.md): An informal introduction to writing
|
|
22
22
|
Fastify plugins.
|
|
23
|
+
+ [Prototype Poisoning](./Prototype-Poisoning.md): A description of how the
|
|
24
|
+
prototype poisoning attack works and is mitigated.
|
|
23
25
|
+ [Recommendations](./Recommendations.md): Recommendations for how to deploy
|
|
24
26
|
Fastify into production environments.
|
|
25
27
|
+ [Serverless](./Serverless.md): Details on how to deploy Fastify applications
|
|
@@ -0,0 +1,391 @@
|
|
|
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
|
+
## A Tale of (prototype) Poisoning
|
|
8
|
+
<a id="pp"></a>
|
|
9
|
+
|
|
10
|
+
This story is a behind-the-scenes look at the process and drama created by a
|
|
11
|
+
particularity interesting web security issue. It is also a perfect illustration
|
|
12
|
+
of the efforts required to maintain popular pieces of open source software and
|
|
13
|
+
the limitations of existing communication channels.
|
|
14
|
+
|
|
15
|
+
But first, if you use a JavaScript framework to process incoming JSON data, take
|
|
16
|
+
a moment to read up on [Prototype
|
|
17
|
+
Poisoning](https://medium.com/intrinsic/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96)
|
|
18
|
+
in general, and the specific [technical
|
|
19
|
+
details](https://github.com/hapijs/hapi/issues/3916) of this issue. I'll explain
|
|
20
|
+
it all in a bit, but since this could be a critical issue, you might want to
|
|
21
|
+
verify your own code first. While this story is focused on a specific framework,
|
|
22
|
+
any solution that uses `JSON.parse()` to process external data is potentially at
|
|
23
|
+
risk.
|
|
24
|
+
|
|
25
|
+
### BOOM
|
|
26
|
+
<a id="pp-boom"></a>
|
|
27
|
+
|
|
28
|
+
Our story begins with a bang.
|
|
29
|
+
|
|
30
|
+
The engineering team at Lob (long time generous supporters of my work!) reported
|
|
31
|
+
a critical security vulnerability they identified in our data validation
|
|
32
|
+
module — [joi](https://github.com/hapijs/joi). They provided some technical
|
|
33
|
+
details and a proposed solution.
|
|
34
|
+
|
|
35
|
+
The main purpose of a data validation library is to ensure the output fully
|
|
36
|
+
complies with the rules defined. If it doesn't, validation fails. If it passes,
|
|
37
|
+
your can blindly trust that the data you are working with is safe. In fact, most
|
|
38
|
+
developers treat validated input as completely safe from a system integrity
|
|
39
|
+
perspective. This is crucial.
|
|
40
|
+
|
|
41
|
+
In our case, the Lob team provided an example where some data was able to sneak
|
|
42
|
+
by the validation logic and pass through undetected. This is the worst possible
|
|
43
|
+
defect a validation library can have.
|
|
44
|
+
|
|
45
|
+
### Prototype in a nutshell
|
|
46
|
+
<a id="pp-nutshell"></a>
|
|
47
|
+
|
|
48
|
+
To understand this story, you need to understand how JavaScript works a bit.
|
|
49
|
+
Every object in JavaScript can have a prototype. It is a set of methods and
|
|
50
|
+
properties it "inherits" from another object. I put inherits in quotes because
|
|
51
|
+
JavaScript isn't really an object oriented language.
|
|
52
|
+
|
|
53
|
+
A long time ago, for a bunch of irrelevant reasons, someone decided that it
|
|
54
|
+
would be a good idea to use the special property name `__proto__` to access (and
|
|
55
|
+
set) an object's prototype. This has since been deprecated but nevertheless,
|
|
56
|
+
fully supported.
|
|
57
|
+
|
|
58
|
+
To demonstrate:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
> const a = { b: 5 };
|
|
62
|
+
> a.b;
|
|
63
|
+
5
|
|
64
|
+
> a.__proto__ = { c: 6 };
|
|
65
|
+
> a.c;
|
|
66
|
+
6
|
|
67
|
+
> a;
|
|
68
|
+
{ b: 5 }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
As you can see, the object doesn't have a `c` property, but its prototype does.
|
|
72
|
+
When validating the object, the validation library ignores the prototype and
|
|
73
|
+
only validates the object's own properties. This allows `c` to sneak in via the
|
|
74
|
+
prototype.
|
|
75
|
+
|
|
76
|
+
Another important part of this story is the way `JSON.parse()` — a utility
|
|
77
|
+
provided by the language to convert JSON formatted text into objects — handles
|
|
78
|
+
this magic `__proto__` property name.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
> const text = '{ "b": 5, "__proto__": { "c": 6 } }';
|
|
82
|
+
> const a = JSON.parse(text);
|
|
83
|
+
> a;
|
|
84
|
+
{ b: 5, __proto__: { c: 6 } }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Notice how `a` has a `__proto__` property. This is not a prototype reference. It
|
|
88
|
+
is a simple object property key, just like `b`. As we've seen from the first
|
|
89
|
+
example, we can't actually create this key through assignment as that invokes
|
|
90
|
+
the prototype magic and sets an actual prototype. `JSON.parse()` however, sets a
|
|
91
|
+
simple property with that poisonous name.
|
|
92
|
+
|
|
93
|
+
By itself, the object created by `JSON.parse()` is perfectly safe. It doesn't
|
|
94
|
+
have a prototype of its own. It has a seemingly harmless property that just
|
|
95
|
+
happens to overlap with a built-in JavaScript magic name.
|
|
96
|
+
|
|
97
|
+
However, other methods are not as lucky:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
> const x = Object.assign({}, a);
|
|
101
|
+
> x;
|
|
102
|
+
{ b: 5}
|
|
103
|
+
> x.c;
|
|
104
|
+
6;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
If we take the `a` object created earlier by `JSON.parse()` and pass it to the
|
|
108
|
+
helpful `Object.assign()` method (used to perform a shallow copy of all the top
|
|
109
|
+
level properties of `a` into the provided empty `{}` object), the magic
|
|
110
|
+
`__proto__` property "leaks" and becomes `x` 's actual prototype.
|
|
111
|
+
|
|
112
|
+
Surprise!
|
|
113
|
+
|
|
114
|
+
Put together, if you get some external text input, parse it with `JSON.parse()`
|
|
115
|
+
then perform some simple manipulation of that object (say, shallow clone and add
|
|
116
|
+
an `id` ), and then pass it to our validation library, anything passed through
|
|
117
|
+
via `__proto__` would sneak in undetected.
|
|
118
|
+
|
|
119
|
+
### Oh joi!
|
|
120
|
+
<a id="pp-oh-joi"></a>
|
|
121
|
+
|
|
122
|
+
The first question is, of course, why does the validation module **joi** ignore
|
|
123
|
+
the prototype and let potentially harmful data through? We asked ourselves the
|
|
124
|
+
same question and our instant thought was "it was an oversight". A bug. A really
|
|
125
|
+
big mistake. The joi module should not have allowed this to happen. But…
|
|
126
|
+
|
|
127
|
+
While joi is used primarily for validating web input data, it also has a
|
|
128
|
+
significant user base using it to validate internal objects, some of which have
|
|
129
|
+
prototypes. The fact that joi ignores the prototype is a helpful "feature". It
|
|
130
|
+
allows validating the object's own properties while ignoring what could be a
|
|
131
|
+
very complicated prototype structure (with many methods and literal properties).
|
|
132
|
+
|
|
133
|
+
Any solution at the joi level would mean breaking some currently working code.
|
|
134
|
+
|
|
135
|
+
### The right thing
|
|
136
|
+
<a id="pp-right-thing"></a>
|
|
137
|
+
|
|
138
|
+
At this point, we were looking at a devastatingly bad security vulnerability.
|
|
139
|
+
Right up there in the upper echelons of epic security failures. All we knew is
|
|
140
|
+
that our extremely popular data validation library fails to block harmful data,
|
|
141
|
+
and that this data is trivial to sneak through. All you need to do is add
|
|
142
|
+
`__proto__` and some crap to a JSON input and send it on its way to an
|
|
143
|
+
application built using our tools.
|
|
144
|
+
|
|
145
|
+
(Dramatic pause)
|
|
146
|
+
|
|
147
|
+
We knew we had to fix joi to prevent this but given the scale of this issue, we
|
|
148
|
+
had to do it in a way that will put a fix out without drawing too much attention
|
|
149
|
+
to it — without making it too easy to exploit — at least for a few days until
|
|
150
|
+
most systems received the update.
|
|
151
|
+
|
|
152
|
+
Sneaking a fix isn't the hardest thing to accomplish. If you combine it with an
|
|
153
|
+
otherwise purposeless refactor of the code, and throw in a few unrelated bug
|
|
154
|
+
fixes and maybe a cool new feature, you can publish a new version without
|
|
155
|
+
drawing attention to the real issue being fixed.
|
|
156
|
+
|
|
157
|
+
The problem was, the right fix was going to break valid use cases. You see, joi
|
|
158
|
+
has no way of knowing if you want it to ignore the prototype you set, or block
|
|
159
|
+
the prototype set by an attacker. A solution that fixes the exploit will break
|
|
160
|
+
code and breaking code tends to get a lot of attention.
|
|
161
|
+
|
|
162
|
+
On the other hand, if we released a proper ([semantically
|
|
163
|
+
versioned](https://semver.org/)) fix, mark it as a breaking change, and add a
|
|
164
|
+
new API to explicitly tell joi what you want it to do with the prototype, we
|
|
165
|
+
will share with the world how to exploit this vulnerability while also making it
|
|
166
|
+
more time consuming for systems to upgrade (breaking changes never get applied
|
|
167
|
+
automatically by build tools).
|
|
168
|
+
|
|
169
|
+
Lose — Lose.
|
|
170
|
+
|
|
171
|
+
### A detour
|
|
172
|
+
<a id="pp-detour"></a>
|
|
173
|
+
|
|
174
|
+
While the issue at hand was about incoming request payloads, we had to pause and
|
|
175
|
+
check if it could also impact data coming via the query string, cookies, and
|
|
176
|
+
headers. Basically, anything that gets serialized into objects from text.
|
|
177
|
+
|
|
178
|
+
We quickly confirmed node default query string parser was fine as well as its
|
|
179
|
+
header parser. I identified one potential issue with base64-encoded JSON cookies
|
|
180
|
+
as well as the usage of custom query string parsers. We also wrote some tests to
|
|
181
|
+
confirm that the most popular third-party query string parser —
|
|
182
|
+
[qs](https://www.npmjs.com/package/qs) — was not vulnerable (it is not!).
|
|
183
|
+
|
|
184
|
+
### A development
|
|
185
|
+
<a id="pp-a-development"></a>
|
|
186
|
+
|
|
187
|
+
Throughout this triage, we just assumed that the offending input with its
|
|
188
|
+
poisoned prototype was coming into joi from hapi, the web framework connecting
|
|
189
|
+
the hapi.js ecosystem. Further investigation by the Lob team found that the
|
|
190
|
+
problem was a bit more nuanced.
|
|
191
|
+
|
|
192
|
+
hapi used `JSON.parse()` to process incoming data. It first set the result
|
|
193
|
+
object as a `payload` property of the incoming request, and then passed that
|
|
194
|
+
same object for validation by joi before being passed to the application
|
|
195
|
+
business logic for processing. Since `JSON.parse()` doesn't actually leak the
|
|
196
|
+
`__proto__` property, it would arrive to joi with an invalid key and fail
|
|
197
|
+
validation.
|
|
198
|
+
|
|
199
|
+
However, hapi provides two extension points where the payload data can be
|
|
200
|
+
inspected (and processed) prior to validation. It is all properly documented and
|
|
201
|
+
well understood by most developers. The extension points are there to allow you
|
|
202
|
+
to interact with the raw inputs prior to validation for legitimate (and often
|
|
203
|
+
security related) reasons.
|
|
204
|
+
|
|
205
|
+
If during one of these two extension points, a developer used `Object.assign()`
|
|
206
|
+
or a similar method on the payload, the `__proto__` property would leak and
|
|
207
|
+
become an actual prototype.
|
|
208
|
+
|
|
209
|
+
### Sigh of relief
|
|
210
|
+
<a id="pp-sigh-of-relief"></a>
|
|
211
|
+
|
|
212
|
+
We were now dealing with a much different level of awfulness. Manipulating the
|
|
213
|
+
payload object prior to validation is not common which meant this was no longer
|
|
214
|
+
a doomsday scenario. It was still potentially catastrophic but the exposure
|
|
215
|
+
dropped from every joi user to some very specific implementations.
|
|
216
|
+
|
|
217
|
+
We were no longer looking at a secretive joi release. The issue in joi is still
|
|
218
|
+
there, but we can now address it properly with a new API and breaking release
|
|
219
|
+
over the next few weeks.
|
|
220
|
+
|
|
221
|
+
We also knew that we can easily mitigate this vulnerability at the framework
|
|
222
|
+
level since it knows which data is coming from the outside and which is
|
|
223
|
+
internally generated. The framework is really the only piece that can protect
|
|
224
|
+
developers against making such unexpected mistakes.
|
|
225
|
+
|
|
226
|
+
### Good news, bad news, no news?
|
|
227
|
+
<a id="pp-good-news-no-news"></a>
|
|
228
|
+
|
|
229
|
+
The good news was that this wasn't our fault. It wasn't a bug in hapi or joi. It
|
|
230
|
+
was only possible through a complex combination of actions that was not unique
|
|
231
|
+
to hapi or joi. This can happen with every other JavaScript framework. If hapi
|
|
232
|
+
is broken, then the world is broken.
|
|
233
|
+
|
|
234
|
+
Great — we solved the blame game.
|
|
235
|
+
|
|
236
|
+
The bad news is that when there is nothing to blame (other than JavaScript
|
|
237
|
+
itself), it is much harder getting it fixed.
|
|
238
|
+
|
|
239
|
+
The first question people ask once a security issue is found is if there is
|
|
240
|
+
going to be a CVE published. A CVE — Common Vulnerabilities and Exposures — is a
|
|
241
|
+
[database](https://cve.mitre.org/) of known security issues. It is a critical
|
|
242
|
+
component of web security. The benefit of publishing a CVE is that it
|
|
243
|
+
immediately triggers alarms and informs and often breaks automated builds until
|
|
244
|
+
the issue is resolved.
|
|
245
|
+
|
|
246
|
+
But what do we pin this to?
|
|
247
|
+
|
|
248
|
+
Probably, nothing. We are still debating whether we should tag some versions of
|
|
249
|
+
hapi with a warning. The "we" is the node security process. Since we now have a
|
|
250
|
+
new version of hapi that mitigate the problem by default, it can be considered a
|
|
251
|
+
fix. But because the fix isn't to a problem in hapi itself, it is not exactly
|
|
252
|
+
kosher to declare older versions harmful.
|
|
253
|
+
|
|
254
|
+
Publishing an advisory on previous versions of hapi for the sole purpose of
|
|
255
|
+
nudging people into awareness and upgrade is an abuse of the advisory process.
|
|
256
|
+
I'm personally fine with abusing it for the purpose of improving security but
|
|
257
|
+
that's not my call. As of this writing, it is still being debated.
|
|
258
|
+
|
|
259
|
+
### The solution business
|
|
260
|
+
<a id="pp-solution-business"></a>
|
|
261
|
+
|
|
262
|
+
Mitigating the issue wasn't hard. Making it scale and safe was a bit more
|
|
263
|
+
involved. Since we knew where harmful data can enter the system, and we knew
|
|
264
|
+
where we used the problematic `JSON.parse()` we could replace it with a safe
|
|
265
|
+
implementation.
|
|
266
|
+
|
|
267
|
+
One problem. Validating data can be costly and we are now planning on validating
|
|
268
|
+
every incoming JSON text. The built-in `JSON.parse()` implementation is fast.
|
|
269
|
+
Really really fast. It is unlikely we can build a replacement that will be more
|
|
270
|
+
secure and anywhere as fast. Especially not overnight and without introducing
|
|
271
|
+
new bugs.
|
|
272
|
+
|
|
273
|
+
It was obvious we were going to wrap the existing `JSON.parse()` method with
|
|
274
|
+
some additional logic. We just had to make sure it was not adding too much
|
|
275
|
+
overhead. This isn't just a performance consideration but also a security one.
|
|
276
|
+
If we make it easy to slow down a system by simply sending specific data, we
|
|
277
|
+
make it easy to execute a [DoS
|
|
278
|
+
attack](https://en.wikipedia.org/wiki/Denial-of-service_attack) at very low
|
|
279
|
+
cost.
|
|
280
|
+
|
|
281
|
+
I came up with a stupidly simple solution: first parse the text using the
|
|
282
|
+
existing tools. If this didn't fail, scan the original raw text for the
|
|
283
|
+
offending string "__proto__". Only if we find it, perform an actual scan of the
|
|
284
|
+
object. We can't block every reference to "__proto__" — sometimes it is
|
|
285
|
+
perfectly valid value (like when writing about it here and sending this text
|
|
286
|
+
over to Medium for publication).
|
|
287
|
+
|
|
288
|
+
This made the "happy path" practically as fast as before. It just added one
|
|
289
|
+
function call, a quick text scan (again, very fast built-in implementation), and
|
|
290
|
+
a conditional return. The solution had negligible impact on the vast majority of
|
|
291
|
+
data expected to pass through it.
|
|
292
|
+
|
|
293
|
+
Next problem. The prototype property doesn't have to be at the top level of the
|
|
294
|
+
incoming object. It can be nested deep inside. This means we cannot just check
|
|
295
|
+
for the presence of it at the top level. We need to recursively iterate through
|
|
296
|
+
the object.
|
|
297
|
+
|
|
298
|
+
While recursive functions are a favorite tool, they could be disastrous when
|
|
299
|
+
writing security-conscious code. You see, recursive function increase the size
|
|
300
|
+
of the runtime call stack. The more times you loop, the longer the call stack
|
|
301
|
+
gets. At some point — KABOOM— you reach the maximum length and the process dies.
|
|
302
|
+
|
|
303
|
+
If you cannot guarantee the shape of the incoming data, recursive iteration
|
|
304
|
+
becomes an open threat. An attacker only needs to craft a deep enough object to
|
|
305
|
+
crash your servers.
|
|
306
|
+
|
|
307
|
+
I used a flat loop implementation that is both more memory efficient (less
|
|
308
|
+
function calls, less passing of temporary arguments) and more secure. I am not
|
|
309
|
+
pointing this out to brag, but to highlight how basic engineering practices can
|
|
310
|
+
create (or avoid) security pitfalls.
|
|
311
|
+
|
|
312
|
+
### Putting it to the test
|
|
313
|
+
<a id="pp-putting-to-test"></a>
|
|
314
|
+
|
|
315
|
+
I sent the code to two people. First to [Nathan
|
|
316
|
+
LaFreniere](https://github.com/nlf) to double check the security properties of
|
|
317
|
+
the solution, and then to [Matteo Collina](https://github.com/mcollina) to
|
|
318
|
+
review the performance. They are among the very best at what they do and often
|
|
319
|
+
my go-to people.
|
|
320
|
+
|
|
321
|
+
The performance benchmarks confirmed that the "happy path" was practically
|
|
322
|
+
unaffected. The interesting findings was that removing the offending values was
|
|
323
|
+
faster then throwing an exception. This raised the question of what should be
|
|
324
|
+
the default behavior of the new module — which I called
|
|
325
|
+
[**bourne**](https://github.com/hapijs/bourne) — error or sanitize.
|
|
326
|
+
|
|
327
|
+
The concern, again, was exposing the application to a DoS attack. If sending a
|
|
328
|
+
request with `__proto__` makes things 500% slower, that could be an easy vector
|
|
329
|
+
to exploit. But after a bit more testing we confirmed that sending **any**
|
|
330
|
+
invalid JSON text was creating a very similar cost.
|
|
331
|
+
|
|
332
|
+
In other words, if you parse JSON, invalid values are going to cost you more,
|
|
333
|
+
regardless of what makes them invalid. It is also important to remember that
|
|
334
|
+
while the benchmark showed the significant % cost of scanning suspected objects,
|
|
335
|
+
the actual cost in CPU time was still in the fraction of milliseconds. Important
|
|
336
|
+
to note and measure but not actually harmful.
|
|
337
|
+
|
|
338
|
+
### hapi ever-after
|
|
339
|
+
<a id="pp-hapi-ever-after"></a>
|
|
340
|
+
|
|
341
|
+
There are a bunch of things to be grateful for.
|
|
342
|
+
|
|
343
|
+
The initial disclosure by the Lob team was perfect. It was reported privately,
|
|
344
|
+
to the right people, with the right information. They followed up with
|
|
345
|
+
additional findings, and gave us the time and space to resolve it the right way.
|
|
346
|
+
Lob also was a major sponsor of my work on hapi over the years and that
|
|
347
|
+
financial support is critical to allow everything else to happen. More on that
|
|
348
|
+
in a bit.
|
|
349
|
+
|
|
350
|
+
Triage was stressful but staffed with the right people. Having folks like
|
|
351
|
+
[Nicolas Morel](https://github.com/Marsup), Nathan, and Matteo, available and
|
|
352
|
+
eager to help is critical. This isn't easy to deal with without the pressure,
|
|
353
|
+
but with it, mistakes are likely without proper team collaboration.
|
|
354
|
+
|
|
355
|
+
We got lucky with the actual vulnerability. What started up looking like a
|
|
356
|
+
catastrophic problem, ended up being a delicate but straight-forward problem to
|
|
357
|
+
address.
|
|
358
|
+
|
|
359
|
+
We also got lucky by having full access to mitigate it at the source — didn't
|
|
360
|
+
need to send emails to some unknown framework maintainer and hope for a quick
|
|
361
|
+
answer. hapi's total control over all of its dependencies proved its usefulness
|
|
362
|
+
and security again. Not using [hapi](http://hapijs.com)? [Maybe you
|
|
363
|
+
should](https://hueniverse.com/why-you-should-consider-hapi-6163689bd7c2).
|
|
364
|
+
|
|
365
|
+
### The after in happy ever-after
|
|
366
|
+
<a id="pp-after-ever-after"></a>
|
|
367
|
+
|
|
368
|
+
This is where I have to take advantage of this incident to reiterate the cost
|
|
369
|
+
and need for sustainable and secure open source.
|
|
370
|
+
|
|
371
|
+
My time alone on this one issue exceeded 20 hours. That's half a working week.
|
|
372
|
+
It came at the end of a month were I already spent over 30 hours publishing a
|
|
373
|
+
new major release of hapi (most of the work was done in December). This puts me
|
|
374
|
+
at a personal financial loss of over $5000 this month (I had to cut back on paid
|
|
375
|
+
client work to make time for it).
|
|
376
|
+
|
|
377
|
+
If you rely on code I maintain, this is exactly the level of support, quality,
|
|
378
|
+
and commitment you want (and lets be honest — expect). Most of you take it for
|
|
379
|
+
granted — not just my work but the work of hundreds of other dedicated open
|
|
380
|
+
source maintainers.
|
|
381
|
+
|
|
382
|
+
Because this work is important, I decided to try and make it not just
|
|
383
|
+
financially sustainable but to grow and expand it. There is so much to improve.
|
|
384
|
+
This is exactly what motivates me to implement the new [commercial licensing
|
|
385
|
+
plan](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-licensing-a-preview-f982662ee898)
|
|
386
|
+
coming in March. You can read more about it
|
|
387
|
+
[here](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-licensing-a-preview-f982662ee898).
|
|
388
|
+
|
|
389
|
+
Of all the time consuming things, security is at the very top. I hope this story
|
|
390
|
+
successfully conveyed not just the technical details, but also the human drama and
|
|
391
|
+
what it takes to keep the web secure.
|
|
@@ -255,7 +255,7 @@ server {
|
|
|
255
255
|
# group specified above. Note the additional headers that forward
|
|
256
256
|
# information about the original request. You might want to set
|
|
257
257
|
# trustProxy to the address of your NGINX server so the X-Forwarded
|
|
258
|
-
|
|
258
|
+
# fields are used by fastify.
|
|
259
259
|
location / {
|
|
260
260
|
# more info: http://nginx.org/en/docs/http/ngx_http_proxy_module.html
|
|
261
261
|
proxy_http_version 1.1;
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## `Content-Type` Parser
|
|
4
4
|
Natively, Fastify only supports `'application/json'` and `'text/plain'` content
|
|
5
|
-
types.
|
|
5
|
+
types. If the content type is not one of these, a `FST_ERR_CTP_INVALID_MEDIA_TYPE`
|
|
6
|
+
error will be thrown.
|
|
7
|
+
|
|
8
|
+
The default charset is `utf-8`. If you need to support different content
|
|
6
9
|
types, you can use the `addContentTypeParser` API. *The default JSON and/or
|
|
7
10
|
plain text parser can be changed or removed.*
|
|
8
11
|
|
|
@@ -181,7 +181,7 @@ callback.
|
|
|
181
181
|
Example:
|
|
182
182
|
```js
|
|
183
183
|
module.exports = function (fastify, opts, done) {
|
|
184
|
-
fastify.decorate('utility', ()
|
|
184
|
+
fastify.decorate('utility', function () {})
|
|
185
185
|
|
|
186
186
|
fastify.get('/', handler)
|
|
187
187
|
|
|
@@ -191,7 +191,7 @@ module.exports = function (fastify, opts, done) {
|
|
|
191
191
|
You can also use `register` inside another `register`:
|
|
192
192
|
```js
|
|
193
193
|
module.exports = function (fastify, opts, done) {
|
|
194
|
-
fastify.decorate('utility', ()
|
|
194
|
+
fastify.decorate('utility', function () {})
|
|
195
195
|
|
|
196
196
|
fastify.get('/', handler)
|
|
197
197
|
|
|
@@ -226,7 +226,7 @@ plugin will support.
|
|
|
226
226
|
const fp = require('fastify-plugin')
|
|
227
227
|
|
|
228
228
|
module.exports = fp(function (fastify, opts, done) {
|
|
229
|
-
fastify.decorate('utility', ()
|
|
229
|
+
fastify.decorate('utility', function () {})
|
|
230
230
|
done()
|
|
231
231
|
}, '0.x')
|
|
232
232
|
```
|
|
@@ -239,7 +239,7 @@ changes it will be your responsibility to update the module, while if you use
|
|
|
239
239
|
`fastify-plugin`, you can be sure about backward compatibility.
|
|
240
240
|
```js
|
|
241
241
|
function yourPlugin (fastify, opts, done) {
|
|
242
|
-
fastify.decorate('utility', ()
|
|
242
|
+
fastify.decorate('utility', function () {})
|
|
243
243
|
done()
|
|
244
244
|
}
|
|
245
245
|
yourPlugin[Symbol.for('skip-override')] = true
|
|
@@ -57,6 +57,9 @@ This operation will add to the request headers the new values that can be read
|
|
|
57
57
|
calling `request.headers.bar`. Moreover, you can still access the standard
|
|
58
58
|
request's headers with the `request.raw.headers` property.
|
|
59
59
|
|
|
60
|
+
> Note: For performance reason on `not found` route, you may see that we will add
|
|
61
|
+
an extra property `Symbol('fastify.RequestAcceptVersion')` on the headers.
|
|
62
|
+
|
|
60
63
|
```js
|
|
61
64
|
fastify.post('/:params', options, function (request, reply) {
|
|
62
65
|
console.log(request.body)
|
package/docs/Reference/Server.md
CHANGED
|
@@ -13,6 +13,7 @@ describes the properties available in that options object.
|
|
|
13
13
|
- [`https`](#https)
|
|
14
14
|
- [`connectionTimeout`](#connectiontimeout)
|
|
15
15
|
- [`keepAliveTimeout`](#keepalivetimeout)
|
|
16
|
+
- [`forceCloseConnections`](#forcecloseconnections)
|
|
16
17
|
- [`maxRequestsPerSocket`](#maxrequestspersocket)
|
|
17
18
|
- [`requestTimeout`](#requesttimeout)
|
|
18
19
|
- [`ignoreTrailingSlash`](#ignoretrailingslash)
|
|
@@ -76,6 +77,9 @@ describes the properties available in that options object.
|
|
|
76
77
|
- [printRoutes](#printroutes)
|
|
77
78
|
- [printPlugins](#printplugins)
|
|
78
79
|
- [addContentTypeParser](#addcontenttypeparser)
|
|
80
|
+
- [hasContentTypeParser](#hasContentTypeParser)
|
|
81
|
+
- [removeContentTypeParser](#removeContentTypeParser)
|
|
82
|
+
- [removeAllContentTypeParsers](#removeAllContentTypeParsers)
|
|
79
83
|
- [getDefaultJsonParser](#getdefaultjsonparser)
|
|
80
84
|
- [defaultTextParser](#defaulttextparser)
|
|
81
85
|
- [errorHandler](#errorhandler)
|
|
@@ -124,6 +128,19 @@ use. Also, when `serverFactory` option is specified, this option is ignored.
|
|
|
124
128
|
|
|
125
129
|
+ Default: `5000` (5 seconds)
|
|
126
130
|
|
|
131
|
+
### `forceCloseConnections`
|
|
132
|
+
<a id="forcecloseconnections"></a>
|
|
133
|
+
|
|
134
|
+
When set to `true` requests with the header `connection: keep-alive` will be
|
|
135
|
+
tracked by the server. Upon [`close`](#close), the server will iterate the
|
|
136
|
+
current persistent connections and [destroy their
|
|
137
|
+
sockets](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketdestroyerror).
|
|
138
|
+
This means the server will shutdown immediately instead of waiting for existing
|
|
139
|
+
persistent connections to timeout first. Important: connections are not
|
|
140
|
+
inspected to determine if requests have been completed.
|
|
141
|
+
|
|
142
|
+
+ Default: `false`
|
|
143
|
+
|
|
127
144
|
### `maxRequestsPerSocket`
|
|
128
145
|
<a id="factory-max-requests-per-socket"></a>
|
|
129
146
|
|
|
@@ -207,7 +224,7 @@ Defines the maximum payload, in bytes, the server is allowed to accept.
|
|
|
207
224
|
Defines what action the framework must take when parsing a JSON object with
|
|
208
225
|
`__proto__`. This functionality is provided by
|
|
209
226
|
[secure-json-parse](https://github.com/fastify/secure-json-parse). See
|
|
210
|
-
|
|
227
|
+
[Prototype Poisoning](../Guides/Prototype-Poisoning.md) for more
|
|
211
228
|
details about prototype poisoning attacks.
|
|
212
229
|
|
|
213
230
|
Possible values are `'error'`, `'remove'` and `'ignore'`.
|
|
@@ -220,7 +237,7 @@ Possible values are `'error'`, `'remove'` and `'ignore'`.
|
|
|
220
237
|
Defines what action the framework must take when parsing a JSON object with
|
|
221
238
|
`constructor`. This functionality is provided by
|
|
222
239
|
[secure-json-parse](https://github.com/fastify/secure-json-parse). See
|
|
223
|
-
|
|
240
|
+
[Prototype Poisoning](../Guides/Prototype-Poisoning.md) for more
|
|
224
241
|
details about prototype poisoning attacks.
|
|
225
242
|
|
|
226
243
|
Possible values are `'error'`, `'remove'` and `'ignore'`.
|
|
@@ -1465,6 +1482,39 @@ content types, e.g. `text/json, application/vnd.oasis.opendocument.text`.
|
|
|
1465
1482
|
fastify.addContentTypeParser('text/json', { asString: true }, fastify.getDefaultJsonParser('ignore', 'ignore'))
|
|
1466
1483
|
```
|
|
1467
1484
|
|
|
1485
|
+
#### hasContentTypeParser
|
|
1486
|
+
<a id="hasContentTypeParser"></a>
|
|
1487
|
+
|
|
1488
|
+
`fastify.hasContentTypeParser(contentType)` is used to check whether there is a content type parser in the current
|
|
1489
|
+
context for the specified content type.
|
|
1490
|
+
|
|
1491
|
+
```js
|
|
1492
|
+
fastify.hasContentTypeParser('text/json')
|
|
1493
|
+
|
|
1494
|
+
fastify.hasContentTypeParser(/^.+\/json$/)
|
|
1495
|
+
```
|
|
1496
|
+
|
|
1497
|
+
#### removeContentTypeParser
|
|
1498
|
+
<a id="removeContentTypeParser"></a>
|
|
1499
|
+
|
|
1500
|
+
`fastify.removeContentTypeParser(contentType)` is used to remove content type parsers in the current context. This
|
|
1501
|
+
method allows for example to remove the both built-in parsers for `application/json` and `text/plain`.
|
|
1502
|
+
|
|
1503
|
+
```js
|
|
1504
|
+
fastify.removeContentTypeParser('application/json')
|
|
1505
|
+
|
|
1506
|
+
fastify.removeContentTypeParser(['application/json', 'text/plain'])
|
|
1507
|
+
```
|
|
1508
|
+
|
|
1509
|
+
#### removeAllContentTypeParsers
|
|
1510
|
+
<a id="removeAllContentTypeParsers"></a>
|
|
1511
|
+
|
|
1512
|
+
The `fastify.removeAllContentTypeParsers()` method allows all content type parsers in the current context to be removed.
|
|
1513
|
+
A use case of this method is the implementation of catch-all content type parser. Before adding this parser with
|
|
1514
|
+
`fastify.addContentTypeParser()` one could call the `removeAllContentTypeParsers` method.
|
|
1515
|
+
|
|
1516
|
+
For more details about the usage of the different content type parser APIs see [here](./ContentTypeParser.md#usage).
|
|
1517
|
+
|
|
1468
1518
|
#### getDefaultJsonParser
|
|
1469
1519
|
<a id="getDefaultJsonParser"></a>
|
|
1470
1520
|
|
|
@@ -1384,7 +1384,7 @@ body parsing happens before the `preHandler` hook.
|
|
|
1384
1384
|
|
|
1385
1385
|
[src](https://github.com/fastify/fastify/blob/main/types/hooks.d.ts#L35)
|
|
1386
1386
|
|
|
1387
|
-
preParsing` is the second hook to be executed in the request lifecycle. The
|
|
1387
|
+
`preParsing` is the second hook to be executed in the request lifecycle. The
|
|
1388
1388
|
previous hook was `onRequest`, the next hook will be `preValidation`.
|
|
1389
1389
|
|
|
1390
1390
|
Notice: in the `preParsing` hook, request.body will always be null, because the
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
Fastify uses a schema-based approach, and even if it is not mandatory we
|
|
5
5
|
recommend using [JSON Schema](https://json-schema.org/) to validate your routes
|
|
6
6
|
and serialize your outputs. Internally, Fastify compiles the schema into a
|
|
7
|
-
highly performant function.
|
|
7
|
+
highly performant function.
|
|
8
|
+
|
|
9
|
+
Validation will only be attempted if the content type is `application-json`,
|
|
10
|
+
as described in the documentation for the [content type parser](./ContentTypeParser.md).
|
|
8
11
|
|
|
9
12
|
> ## ⚠ Security Notice
|
|
10
13
|
> Treat the schema definition as application code. Validation and serialization
|
package/fastify.d.ts
CHANGED
|
@@ -98,6 +98,7 @@ export type FastifyServerOptions<
|
|
|
98
98
|
connectionTimeout?: number,
|
|
99
99
|
keepAliveTimeout?: number,
|
|
100
100
|
maxRequestsPerSocket?: number,
|
|
101
|
+
forceCloseConnections?: boolean,
|
|
101
102
|
requestTimeout?: number,
|
|
102
103
|
pluginTimeout?: number,
|
|
103
104
|
bodyLimit?: number,
|
|
@@ -112,6 +113,7 @@ export type FastifyServerOptions<
|
|
|
112
113
|
caseSensitive?: boolean,
|
|
113
114
|
requestIdHeader?: string,
|
|
114
115
|
requestIdLogLabel?: string;
|
|
116
|
+
jsonShorthand?: boolean;
|
|
115
117
|
genReqId?: <RequestGeneric extends RequestGenericInterface = RequestGenericInterface>(req: FastifyRequest<RequestGeneric, RawServer, RawRequestDefaultExpression<RawServer>>) => string,
|
|
116
118
|
trustProxy?: boolean | string | string[] | number | TrustProxyFunction,
|
|
117
119
|
querystringParser?: (str: string) => { [key: string]: unknown },
|
|
@@ -144,7 +146,7 @@ export type FastifyServerOptions<
|
|
|
144
146
|
return503OnClosing?: boolean,
|
|
145
147
|
ajv?: {
|
|
146
148
|
customOptions?: AjvOptions,
|
|
147
|
-
plugins?: Function[]
|
|
149
|
+
plugins?: (Function | [Function, unknown])[]
|
|
148
150
|
},
|
|
149
151
|
frameworkErrors?: <RequestGeneric extends RequestGenericInterface = RequestGenericInterface>(
|
|
150
152
|
error: FastifyError,
|