Fastify is a popular web framework built for Node.js. Its minimal overhead and flexible plugin system have made it a top choice for developers looking for fast APIs. But like all software, it isn't immune to security problems. In late 2022, a vulnerability was discovered—CVE-2022-41919—that allowed attackers to bypass CORS pre-flight protection and perform Cross-Site Request Forgery (CSRF) attacks on APIs using Fastify.
In this post, I'll break down what this means, show you how the exploit works with code snippets, and explain how you can stay protected.
Quick Overview: What’s This All About?
CVE-2022-41919 is about Fastify not correctly checking the type of incoming requests on protected endpoints. Specifically, if your API expects JSON, it might only accept requests with the Content-Type: application/json header. However, an attacker can send a request with another common content type (like text/plain or multipart/form-data), still sneak their request through, and bypass CORS checks designed for browsers. This opens the door for tricking a user's browser into performing actions the user didn't intend—a classic CSRF attack.
When you define a Fastify route like this
fastify.post('/change-email', {
schema: {
body: {
type: 'object',
properties: {
email: { type: 'string', format: 'email' }
},
required: ['email']
}
}
}, async (request, reply) => {
// Change user email logic
return { success: true };
});
You might expect that only Content-Type: application/json is accepted. This is a reasonable assumption, since the body schema is for JSON.
2. The Pre-Flight Misunderstanding
Browsers perform a "pre-flight" request (an OPTIONS request) before sending sensitive (like, cross-origin) requests that use non-simple headers (like application/json). If the response doesn't explicitly allow the real request, the browser blocks it.
But requests with "simple" content types (application/x-www-form-urlencoded, multipart/form-data, and text/plain) *do not trigger* a pre-flight. That means anyone can send these from an HTML form, and the browser won't ask permission from the target site first.
An attacker can create a malicious HTML form like this
<form action="https://your-fastify-site.com/change-email"; method="POST" enctype="text/plain">
<input type="text" name="email" value="attacker@example.com">
<button type="submit">Submit</button>
</form>
If a logged-in user visits the attacker's page, their browser submits this form. Fastify receives the request, and unless you specifically check for Content-Type: application/json, your route handler may process the request—even though it didn’t come with the right headers or a CORS pre-flight!
Let's imagine you have this Fastify server
// server.js
const fastify = require('fastify')();
fastify.post('/change-email', async (req, res) => {
console.log(req.body); // will print parsed body, even if sent as text/plain or urlencoded
// Suppose some logic changes the email without further validation
return { status: 'Email changed' };
});
fastify.listen({ port: 300 });
Now, on the attacker’s site
<!-- attacker.html -->
<form target="dummy" action="http://localhost:300/change-email"; method="POST" enctype="text/plain">
<input name="email" value="stolen@example.com">
<input type="submit">
</form>
If a user (already logged in to your site) visits this page and submits the form, your Fastify server might process the request, even though CORS should prevent cross-origin actions. The pre-flight never happened, so your CORS protection never kicked in!
Change user data by tricking victims into submitting forged requests.
- Bypass CORS rules you set—since the browser doesn't send a pre-flight when the "simple" content types are used.
- Abuse endpoints that should only accept JSON, with no server-side verification about the request's Content-Type.
In short: This is a real, practical CSRF risk for any Fastify app not actively blocking non-JSON content types on sensitive endpoints.
v3.29.4 (for Fastify 3.x)
View the official advisory on GitHub.
How does the patch work?
Upcoming versions require the proper content type header and handle application/json more strictly. This makes it much harder for attackers to sneak malformed requests past your routes.
Enforce Content-Type Server Side
Make sure your routes explicitly check for Content-Type: application/json on endpoints that should only accept JSON. Example middleware:
if (
request.routerPath === '/change-email' && // Protect sensitive endpoint
request.headers['content-type'] !== 'application/json'
`
3. Add CSRF Protection With @fastify/csrf
`bash
npm install @fastify/csrf
const fastify = require('fastify')();
fastify.register(require('@fastify/csrf'), { sessionPlugin: '@fastify/session' /* configure storage */ });
References
- Fastify Security Advisory GHSA-5pg9-7pw8-w73v (GitHub)
- Official Fastify Documentation: CORS
- OWASP: Cross-Site Request Forgery (CSRF)
- CVE Record on NVD
Conclusion
CVE-2022-41919 is a strong reminder not to assume CORS is all you need to stop CSRF—especially with new web frameworks and their plugin systems. Always update dependencies, strictly validate request headers and content types on sensitive routes, and include CSRF tokens—especially for state-changing operations.
Protect your APIs. Fastify is powerful, but defense-in-depth keeps your users truly safe. If you use Fastify, upgrade now and ensure you’re not open to these classic—but dangerous—web attacks.
Timeline
Published on: 11/22/2022 20:15:00 UTC
Last modified on: 11/26/2022 03:35:00 UTC