Best Practices

Fastify Security Posture in 2024

Fastify hit version 5.0 in September 2024 with a slimmer core, a plugin model that encourages correctness, and a security track record that genuinely distinguishes it from the Express crowd. Here is what I have learned auditing Fastify apps this year.

Shadab Khan
Security Engineer
7 min read

The first time I audited a Fastify application, I was braced for the Express experience — the dependency tree in the thousands, three abandoned middleware packages, a rate limiter that did not share state across pods. What I found instead was 217 total dependencies, every plugin using the official fastify-plugin wrapper, and a JSON schema on every route. I asked the lead engineer if this was normal for his team. He said, "Fastify kind of forces you into it."

That is the Fastify pitch in practice. The framework has opinions, and those opinions happen to overlap with application security best practices. That does not make Fastify apps automatically secure — I still find plenty of issues — but the starting point is meaningfully better than Express. This piece covers what I look for when I audit a Fastify app in 2024, including the 5.0 release and the security implications of its architectural choices.

Fastify 5.0 and the Node.js support boundary

Fastify 5.0 shipped on September 17, 2024, and dropped support for Node.js 18. That is not a minor detail — Node.js 18 went end-of-life in April 2025, and Fastify 5.0 required Node.js 20 or later at release. If your Fastify app is still on 4.x because you cannot upgrade Node, that is your first action item. Node 18 will not receive security patches after its EOL, which means OpenSSL vulnerabilities, HTTP parsing bugs, and anything else in the runtime becomes your problem.

The 5.0 release also tightened the type system for schemas and removed a few deprecated APIs. None of those removals were security-critical, but the cumulative effect of the tightening is that common misuse patterns get caught at startup rather than at runtime.

Schema validation is the headline security feature

Every Fastify route accepts a JSON Schema for its body, params, querystring, and headers. The framework uses Ajv to validate inputs against the schema before your handler runs. If the validation fails, the request returns 400 automatically. This is the single most important security property of Fastify applications, and it is the thing Express shops spend a year trying to replicate with Zod or Joi wrappers.

The catch is that schemas are only as tight as you write them. I have seen Fastify apps with schemas that declare type: 'object' and nothing else, effectively accepting any payload. Write strict schemas: additionalProperties: false, explicit required arrays, format validators on strings that represent emails, URIs, or UUIDs. Run a schema audit periodically — I script one using jq over the route configurations and flag any schema that accepts unbounded strings or arbitrary objects.

Plugin encapsulation and its security implications

Fastify's plugin system creates encapsulation contexts. A plugin registered within a scope cannot see decorators, hooks, or schemas from outside its scope unless they are explicitly exposed. This is a real security win — you can isolate an admin route tree from the public API tree, and misconfigurations in one scope cannot leak into the other.

The pattern I recommend: split your app into a public plugin and an authenticated plugin, with the auth hook registered only inside the authenticated plugin. This way, a new public route cannot accidentally skip auth by being registered in the wrong place, because the decorator it would need to check the token is not visible from the public scope. I have fixed exactly this bug twice in Express codebases and never in Fastify.

The fastify-plugin wrapper and why it matters

If you write a plugin that adds decorators or hooks that should be visible outside its scope, wrap it with fastify-plugin. This "breaks" encapsulation intentionally. The anti-pattern I see in audits is developers wrapping every plugin with fastify-plugin because they got tired of decorators not being visible, effectively defeating the encapsulation model. Ask your team why any given plugin is wrapped. If the answer is "I don't remember," unwrap it.

The plugin ecosystem: a shorter but still real tail

Fastify has an official plugins registry, and the core team maintains roughly forty plugins under the @fastify/* namespace. These are well-maintained and tracked for CVEs. The problem is the community plugins — the ones that live on individual GitHub accounts, get published once, and then languish.

When I audit a Fastify app, I sort the plugins into three buckets: official @fastify/*, community plugins with more than five contributors and a recent publish, and everything else. The "everything else" bucket gets a justification per plugin. If you cannot justify keeping a plugin from an unmaintained repository, rewrite its functionality inline or replace it.

Rate limiting with @fastify/rate-limit

The official rate limit plugin is the right choice, and it supports a Redis store out of the box. Configuration mistakes are still common. The default keyGenerator uses req.ip, which means you must configure trustProxy correctly when running behind a load balancer. Fastify's trustProxy option accepts specific trusted hop counts or IP ranges — do not set it to true in production, because that trusts arbitrary X-Forwarded-For headers and lets any client spoof their IP for rate-limiting purposes.

I have seen apps rate-limiting per-user based on req.ip that were being trivially bypassed because trustProxy: true was set and the load balancer did not strip incoming X-Forwarded-For headers. The fix is a two-line config change, but you need to know to look for it.

CVEs that hit Fastify and its plugins in 2023-2024

CVE-2023-46402 affected fastify-static — a maintained plugin — with a path traversal issue on case-insensitive filesystems. Patched in version 6.11.0 in October 2023. If you serve static files on macOS or Windows without that patch, an attacker can escape the static directory.

CVE-2024-28849 in follow-redirects (a transitive dep of many HTTP clients Fastify apps use) leaked Authorization headers on cross-host redirects. Patched in 1.15.6 in March 2024. This one bites Fastify apps that use Axios or Got for outbound calls more than Fastify itself, but it is worth checking.

The core Fastify framework has had very few direct CVEs in the last three years. That is either because the code is solid or because security researchers have not looked hard; the behavior of the codebase suggests the former, but I would not bet a customer's data on it.

Content type sniffing and JSON type enforcement

Fastify parses JSON only for Content-Type: application/json by default, which prevents a class of content-type confusion attacks. If you add custom content type parsers, be explicit about which content types they accept. I have seen teams add a text/plain parser that accepted any non-JSON body and forwarded it to a handler that expected structured data — that is a parse-once-interpret-twice bug waiting to happen.

How Safeguard Helps

Safeguard catalogs every plugin in your Fastify tree, separating official @fastify/* packages from community plugins and flagging those with no recent commits or unmaintained dependencies. Reachability analysis tells you which plugins register routes that are actually reachable in production, letting you retire ones that are installed but never invoked. Griffin AI correlates incoming CVEs against your plugin inventory and turns a feed advisory into a specific ticket with a suggested upgrade path. Policy gates block pull requests that add plugins from unofficial namespaces without an exception, so your Fastify dependency story stays as tight five years from now as it is today.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.