Rate Limiting Bypass in Adonisjs with Api Keys
Rate Limiting Bypass in Adonisjs with Api Keys — how this specific combination creates or exposes the vulnerability
A rate limiting bypass in an AdonisJS application that uses API keys commonly arises when limits are enforced per route or per IP, but not consistently tied to the authenticated API key identity. AdonisJS does not apply rate limiting automatically to API key validation logic unless explicitly configured. If API key verification occurs after the rate limit check, or if the limit is scoped to IP address rather than to the key itself, an attacker can rotate keys to exhaust the quota without being throttled.
Consider an endpoint that first validates the API key header to determine the associated tenant or user, then performs business logic. If rate limiting is implemented at the route level using a generic in-memory store or a shared Redis counter without including the API key as part of the rate limit key, requests from different keys are counted separately. This allows an attacker with multiple keys to issue parallel or sequential requests, each appearing within the allowed limit, while cumulatively overwhelming the backend.
Insecure implementations may also leak information via response timing or status codes. If a key is rejected due to being disabled or revoked before the rate limiter runs, the response may differ from a valid key that has hit its quota, enabling enumeration. Similarly, missing validation on the key format can lead to key confusion or injection, where an attacker substitutes a valid key for another identity, bypassing tenant isolation and rate limits that depend on identity.
A concrete attack pattern involves an unauthenticated probe of the API to discover key acceptance behavior, followed by rapid cycling of keys to exploit the lack of a composite key+identifier rate limit. This maps to common OWASP API Top 10 categories such as Broken Object Level Authorization (BOLA) and excessive resource consumption, and may be observable in scans that flag missing rate limiting on authenticated paths or inconsistent limits across authentication methods.
middleBrick detects this class of risk by correlating OpenAPI/Swagger specifications (including $ref resolution for securitySchemes and path items) with runtime behavior across the unauthenticated attack surface. The tool checks whether rate limiting is applied before authentication, whether the limit scope includes the API key, and whether responses vary in ways that disclose key validity. Findings include severity, contextual guidance, and references to frameworks such as OWASP API Top 10 to support remediation planning.
Api Keys-Specific Remediation in Adonisjs — concrete code fixes
To remediate rate limiting bypass risks when using API keys in AdonisJS, enforce limits on the key itself and ensure validation occurs before business logic. Use a stable key extraction method and make the rate limit key include the normalized key value to prevent key rotation abuse.
Example secure API key setup with consistent identification and rate limiting:
// config/api_keys.ts
export default {
provider: 'redis',
keyHeader: 'X-API-Key',
rateLimitWindowMs: 60_000,
rateLimitMax: 100,
};
Middleware that validates the key early and binds rate limiting to the key:
// start/hooks/api-key-middleware.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import ApiKeyService from 'App/Services/ApiKeyService'
import RateLimiter from 'App/Services/RateLimiter'
export default async function apiKeyMiddleware(ctx: HttpContextContract) {
const key = ctx.request.header('X-API-Key')
if (!key || typeof key !== 'string') {
return ctx.response.unauthorized({ error: 'api_key_missing' })
}
const identity = await ApiKeyService.lookupByKey(key.trim())
if (!identity || identity.status !== 'active') {
return ctx.response.unauthorized({ error: 'invalid_api_key' })
}
const allowed = await RateLimiter.allow({
subject: identity.id,
windowMs: 60_000,
max: 100,
keyPrefix: 'apikey_rl',
})
if (!allowed) {
return ctx.response.tooManyRequests({ error: 'rate_limit_exceeded' })
}
ctx.auth.user = identity.user
await ctx.next()
}
Rate limiter service using Redis with the API key identity as part of the key to ensure shared quota enforcement:
// app/Services/RateLimiter.ts
import Redis from '@ioc:Adonis/Addons/Redis'
export default class RateLimiter {
static async allow(params: { subject: string; windowMs: number; max: number; keyPrefix: string }) {
const { subject, windowMs, max, keyPrefix } = params
const key = `${keyPrefix}:${subject}`
const current = await Redis.incr(key)
if (current === 1) {
await Redis.expire(key, Math.ceil(windowMs / 1000))
}
return current <= max
}
}
In routes definition, apply the middleware before sensitive routes and avoid duplicating limits at the route level without key scope:
// start/routes.ts
Route.group(() => {
Route.get('/reports/:id', ReportsController.show)
Route.post('/data', DataController.store)
})
.middleware(['apiKey'])
.prefix('api/v1')
Additionally, normalize keys before storage and comparison to avoid case-sensitive bypasses, and ensure that the same key value always maps to the same identity. When using multiple environments or tenants, include a tenant or environment prefix in the rate limit key to prevent cross-tenant quota sharing. middleBrick supports scanning these configurations by analyzing the OpenAPI spec and runtime behavior; its dashboard can track scores over time, while the CLI allows integration into scripts and the GitHub Action can fail builds if security thresholds are not met.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |