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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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