Prototype Pollution in Adonisjs with Api Keys
Prototype Pollution in Adonisjs with Api Keys — how this specific combination creates or exposes the vulnerability
Prototype pollution in AdonisJS can intersect with API key handling when request payloads or configuration objects are merged into shared prototypes. If an endpoint accepts user input to update settings or extend objects used by API key validation logic, an attacker can inject properties such as __proto__, constructor.prototype, or prototype to modify behavior across the application. This becomes critical when API keys are stored or compared on shared objects, or when key-validation utilities are implemented as reusable modules attached to prototypes.
For example, consider an AdonisJS route that merges request body into a configuration object used for API key verification:
// routes.ts or controller handler
import { schema } from '@ioc:Adonis/Core/Validator'
const apiKeySchema = schema.create({
api_key: schema.string(),
config: schema.object({}, {
// vulnerable: allows arbitrary keys
allowNamespaced: schema.boolean.optional(),
}).optional(),
})
export async function updateKeyConfig({ request, response }: HttpContextContract) {
const payload = request.validate({ schema: apiKeySchema })
// Dangerous: merging user input into a shared/prototype-based config
Object.assign(SharedConfig.prototype.defaults, payload.config)
// API key check later uses SharedConfig.prototype.defaults
const isValid = validateApiKey(payload.api_key)
return response.ok({ isValid })
}
If an attacker sends { "config": { "__proto__": { "isAdmin": true } } }, the merged prototype gains an isAdmin property. If validateApiKey or related authorization logic checks inherited properties (e.g., config.isAdmin), the attacker can bypass intended restrictions. This pattern is relevant when API key scopes or roles are derived from prototype-inherited defaults, effectively enabling privilege escalation without a direct IDOR on the key itself.
The 12 security checks in middleBrick run in parallel and would flag this as BOLA/IDOR and Property Authorization risk when it observes prototype-modifying operations on shared objects influenced by API key–related logic. The scanner cross-references OpenAPI/Swagger 2.0/3.0/3.1 definitions with runtime behavior, detecting dangerous merges even when the API spec describes only key submission without detailing server-side object handling.
Additionally, if an AdonisJS application exposes an unauthenticated endpoint that returns API key metadata or configuration, and that endpoint processes user-influenced input into prototype chains, middleBrick’s LLM/AI Security checks help identify whether system prompt leakage or output data exfiltration could compound the issue by exposing key-related artifacts.
Api Keys-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on preventing prototype pollution at the point where user input merges into objects used by API key validation, and on ensuring API keys are handled as opaque, isolated values.
- Use strict schema validation that disallows prototype pollution vectors. In AdonisJS, prefer the built-in validator with schemas that do not allow arbitrary keys.
// routes.ts — safe schema that rejects prototype keys
import { schema } from '@ioc:Adonis/Core/Validator'
const safeApiKeySchema = schema.create({
api_key: schema.string({}, { trim: true }),
config: schema.object({
// explicitly list allowed fields; do not use "any"
allowCache: schema.boolean.optional(),
ttl: schema.number.optional(),
}, { allowUndefined: true }), // do not allow arbitrary keys
})
export async function updateKeyConfigSafe({ request, response }: HttpContextContract) {
const payload = await request.validate({ schema: safeApiKeySchema })
// Apply only known fields; avoid merging into prototypes
const config = {
allowCache: payload.config?.allowCache ?? false,
ttl: payload.config?.ttl ?? 3600,
}
// Use config as a plain object, not attached to prototypes
await storeKeyConfig(config)
const isValid = validateApiKey(payload.api_key)
return response.ok({ isValid })
}
- Avoid
Object.assignor spread on shared prototypes. Instead, work with plain objects or use defensive copies.
// service.ts — safe handling
export function storeKeyConfig(input: Record<string, unknown>) {
// Defensive copy; do not merge into any prototype chain
const config = { ...input } // only copies own enumerable properties
// Persist config without linking to Object.prototype extensions
writeConfigToVault(config)
}
- Treat API keys as secrets; do not embed them in objects that become part of prototypes. Validate and compare keys using constant-time checks where feasible, and ensure key-related configuration is scoped to request context or isolated singletons.
// auth.ts — constant-time comparison stub
import { timingSafeEqual } from 'node:crypto'
export function validateApiKey(candidate: string): boolean {
const expected = getStoredKeyHash() // Buffer
const candidateBuf = Buffer.from(candidate)
// Ensure buffers are same length to prevent timing leaks
if (candidateBuf.length !== expected.length) return false
return timingSafeEqual(candidateBuf, expected)
}
middleBrick’s CLI (middlebrick scan <url>) can be integrated into scripts to verify that endpoints accepting API key configuration do not exhibit prototype pollution patterns. The Pro plan’s continuous monitoring can alert when new endpoints introduce risky merges, and the GitHub Action can fail builds if such patterns are detected in OpenAPI specs or runtime behavior.