Skip to content

False positive: js/missing-rate-limiting — global @fastify/rate-limit not recognized when the server uses .withTypeProvider() #22412

Description

@dtorres-fgf

Description of the false positive

js/missing-rate-limiting flags route handlers that are, in fact, rate-limited by a global @fastify/rate-limit registration ({ global: true } — the plugin's documented default, which installs an onRequest limiter for every route on the server).

@fastify/rate-limit is modeled (the FastifyRateLimiter class in MissingRateLimiting.qll), and for a plain fastify() server CodeQL correctly recognizes the global limiter as guarding the routes. But when the server instance is created via .withTypeProvider() — the standard Fastify type-provider pattern (e.g. @fastify/type-provider-typebox) — the guard linkage is lost, and every expensive/authorizing route handler is flagged even though the global limiter covers it at runtime.

Because .withTypeProvider() is used by essentially every Fastify + TypeBox project, this affects a broad class of real-world apps that use global rate limiting.

Evidence (CodeQL CLI, bundle 2.26.3 — latest at time of writing)

Controlled experiment building a CodeQL database from a real TypeScript Fastify app (routes defined as TypeBox-typed plugin modules registered under prefixes; a single global @fastify/rate-limit), running only js/missing-rate-limiting:

App construction Global limiter Result on the route handlers
Fastify(opts).withTypeProvider<TypeBoxTypeProvider>() present flagged (false positive)
Fastify(opts) — no .withTypeProvider() present not flagged (correct)
Fastify(opts) — no .withTypeProvider() removed flagged (correct — genuinely unguarded)

Rows 2 and 3 confirm the handlers are recognized and the guard is normally detected. Row 1 vs row 2 isolates .withTypeProvider() as the only change that turns correct detection into a false positive.

Code sample (framework-generic; the affected app is closed-source)

import Fastify from 'fastify'
import rateLimit from '@fastify/rate-limit'
import type { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import v1Routes from './versions/v1' // registers TypeBox-typed route plugins under nested scopes

const app = Fastify(opts).withTypeProvider<TypeBoxTypeProvider>()
await app.register(rateLimit, { global: true, max: 500, timeWindow: '1 minute' })
await app.register(v1Routes, { prefix: '/api/v1' })
// → handlers inside v1Routes are flagged js/missing-rate-limiting,
//   although the global limiter above guards them at runtime.
// Removing `.withTypeProvider()` (only) clears the false positives.

Likely area

The guard relationship between FastifyRateLimiter.getRoutingNode() and the route handlers when the server node is a .withTypeProvider() method-call result rather than the raw StandardServerDefinition — see javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll and .../security/dataflow/MissingRateLimiting.qll. Isolated minimal apps (including .withTypeProvider() + nested register() + typed plugin modules) are recognized correctly; reproducing the FP requires .withTypeProvider() plus a composed multi-plugin app, which points at how the type-provider'd server instance maps into the routing model.

Versions: @fastify/rate-limit 10.3.0, fastify 5.x, @fastify/type-provider-typebox; CodeQL bundle 2.26.3.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions