Prototype Pollution in Adonisjs with Mutual Tls
Prototype Pollution in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
Prototype pollution in AdonisJS can intersect with Mutual TLS (mTLS) in ways that amplify risk when untrusted input reaches server-side object construction or merge routines, even when mTLS provides channel-level authentication. mTLS ensures that both client and server present valid certificates, which strongly authenticates the peer. However, mTLS does not automatically sanitize application-level data. If an AdonisJS application accepts JSON payloads from an authenticated mTLS client and merges those payloads into prototype-based objects (e.g., configuration objects, request context, or models), an attacker who possesses a valid certificate can supply crafted properties that mutate Object.prototype or other shared prototypes.
For example, consider an endpoint that updates user preferences by deeply merging request body with a defaults object. With mTLS, the server may trust the client identity but still process malicious keys like __proto__, constructor.prototype, or constructor.prototype.polluted. Because AdonisJS often uses JavaScript object merging (e.g., via lodash’s merge or manual spread operations), these keys can propagate into shared prototypes if the merge strategy does not explicitly filter or transform them. This can lead to behavior changes across requests, such as injecting falsy guards, altering validation logic, or enabling insecure defaults, which may facilitate further attacks like injection or privilege escalation. The vulnerability is not in mTLS itself but in how the application handles data after mTLS authentication.
Real-world patterns that commonly surface in AdonisJS include using route parameters or body fields to dynamically set object paths (e.g., set(object, path, value)) without restricting key names. If an attacker can control the path and the application merges this into a prototype-rooted structure, they can write to Object.prototype or other global objects. In an mTLS-enabled API, this risk persists because mTLS focuses on peer identity, not input validation. Therefore, even with mTLS enforced, developers must apply strict schema validation and avoid unsafe merging utilities that do not protect against prototype keys.
Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes
Remediation centers on treating mTLS-authenticated inputs as untrusted for object-level operations. In AdonisJS, use strict schema validation (e.g., with ajv or the built-in Validator) and avoid merging untrusted data into shared objects. Below are concrete code examples demonstrating secure practices with mTLS-aware context handling.
Example 1: Enforcing mTLS and validating input before merge
Configure AdonisJS to require client certificates and validate the structure of incoming JSON. Use a schema that explicitly blocks prototype-polluting keys.
// start/hooks.ts or relevant bootstrap file
import { defineConfig } from '@adonisjs/core/app'
export default defineConfig({
https: {
enabled: true,
cert: '/path/to/server-cert.pem',
key: '/path/to/server-key.pem',
ca: '/path/to/ca.pem',
requestCert: true,
rejectUnauthorized: true, // enforce mTLS
},
})
// resources/validators/preferences.ts
import { schema } from '@ioc:Adonis/Core/Validator'
export const preferencesSchema = schema.create({
type: 'object',
additionalProperties: false,
properties: {
theme: schema.string.optional(),
notifications: schema.boolean.optional(),
},
// explicitly reject any unknown keys that could be used for pollution
})
// controllers/PreferencesController.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { preferencesSchema } from 'App/Validators/preferences'
export default class PreferencesController {
public async update({ request, auth }: HttpContextContract) {
const clientCert = request.$ssl?.clientCertificate // example mTLS-derived identity
if (!clientCert) {
throw new Error('mTLS certificate required')
}
const payload = await request.validate({ schema: preferencesSchema })
// Safe merge: only known keys are retained
const updated = { ...auth.user!.preferences, ...payload }
await auth.user!.merge({ preferences: updated }).save()
return updated
}
}
Example 2: Secure deep merge utility that filters prototype keys
If merging is necessary, implement a custom merge that omits dangerous keys instead of relying on lodash.merge or spread on potentially polluted objects.
// utils/safeMerge.ts
export function safeMerge(target: Record, source: Record): Record {
const blockedKeys = new Set(['__proto__', 'constructor', 'prototype'])
const result = { ...target }
for (const key of Object.keys(source)) {
if (blockedKeys.has(key)) {
continue // drop prototype-polluting keys
}
const srcVal = source[key]
const tgtVal = result[key]
if (srcVal && typeof srcVal === 'object' && !Array.isArray(srcVal) && tgtVal && typeof tgtVal === 'object') {
result[key] = safeMerge(tgtVal, srcVal)
} else {
result[key] = srcVal
}
}
return result
}
// usage in controller
import { safeMerge } from 'App/Utils/safeMerge'
public async update({ request, auth }: HttpContextContract) {
const raw = request.only(['theme', 'notifications', '__proto__']) // example input
const merged = safeMerge(auth.user!.preferences || {}, raw)
await auth.user!.merge({ preferences: merged }).save()
}
Example 3: mTLS identity binding to prevent unauthorized authenticated abuse
Even with mTLS, correlate certificate metadata with application permissions and avoid using certificate-derived claims to bypass validation.
// middleware/ensureMtlsIdentity.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default async function ensureMtlsIdentity({ request, auth }: HttpContextContract) {
const cert = request.$ssl?.clientCertificate
if (!cert) {
return response.unauthorized({ error: 'Client certificate required' })
}
// Example: map certificate fingerprint to user roles, do not trust raw cert fields for object merging
const fingerprint = cert.fingerprint // hypothetical property
if (!await isAllowedFingerprint(fingerprint)) {
throw new Error('Unauthorized mTLS identity')
}
}
// Apply to routes
Route.resource('preferences', 'PreferencesController')
.middleware({ before: ['ensureMtlsIdentity'] })
These examples show how to combine mTLS enforcement with disciplined input validation and safe merging to mitigate prototype pollution risks in AdonisJS.