Api Key Exposure in Adonisjs with Mutual Tls
Api Key Exposure in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
When an AdonisJS application uses Mutual TLS (mTLS) for client authentication, developers may assume the transport is fully secured and inadvertently expose API keys in a way mTLS cannot protect. mTLS ensures the client possesses a valid certificate, but it does not prevent the client from sending sensitive authorization data—such as an API key—inside HTTP headers, cookies, or the request body. If an API key is passed in an Authorization header (e.g., Authorization: ApiKey abc123) while mTLS is enforced, the key is still present in plaintext within the application’s request handling layer. Should logs, error messages, or monitoring integrations capture that header, the key can be leaked beyond the intended access boundary.
Another exposure path arises when AdonisJS applications proxy or forward requests internally after mTLS client validation. For example, a gateway may authenticate the client certificate, then forward the request to a downstream service including the original API key header. If the downstream service logs requests or if the forwarding logic is misconfigured, the key can be stored or transmitted insecurely. MiddleBrick’s scan checks for such exposure by inspecting whether API keys appear in HTTP headers that are transmitted or logged after mTLS authentication, flagging cases where keys are present in unencrypted or uncontrolled contexts despite transport-layer mutual authentication.
The interplay of mTLS and API keys also matters in error handling. AdonisJS may return stack traces or validation errors that include header values when exceptions occur. If an API key is present in an incoming header and an unhandled exception is thrown, the key can be reflected in error responses or logs. MiddleBrick’s Data Exposure checks specifically look for sensitive values in responses and logs, identifying whether API keys inadvertently surface alongside mTLS-authenticated sessions. This combination—mTLS for client identity plus key-based authorization—can create a false sense of security if key handling is not explicitly hardened.
Additionally, applications may embed API keys in query parameters or cookies while using mTLS for channel binding. Query parameters are often recorded in server logs and browser histories, and cookies may be duplicated across services. Even with mTLS preventing man-in-the-middle attacks, these storage and transmission practices can lead to persistence and accidental disclosure. MiddleBrick evaluates whether API keys are present in query strings or cookie values after successful mTLS negotiation, highlighting insecure storage and logging practices that persist despite strong transport authentication.
Finally, configuration mistakes in AdonisJS can weaken the protection mTLS offers. For instance, enabling verbose logging for debugging purposes or misconfiguring trust proxy settings may cause the framework to log full headers, including API keys, to disk or stdout. MiddleBrick’s scan validates runtime behavior by analyzing whether API keys are present in observable outputs following mTLS-authenticated requests, providing findings that help developers understand exactly how keys can be exposed even when mutual authentication is in place.
Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes
To reduce exposure risk, handle API keys outside the request flow after mTLS authentication and avoid echoing them in logs or responses. Use environment-based configuration and explicit header stripping where appropriate.
Example 1: mTLS-only transport without API keys in headers
// start/server.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class AuthController {
public async validateMtlsOnly({ request, response }: HttpContextContract) {
// Rely on mTLS client certificate for identity; do not expect an API key header.
const clientCert = request.$ssl?.clientCert
if (!clientCert) {
return response.unauthorized({ error: 'Client certificate required' })
}
// Do not read or forward API keys from headers.
response.send({ ok: true, client: clientCert.subject })
}
}
Example 2: Reject requests containing API key headers when mTLS is used
// start/middleware/forbid-api-key.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default function forbidApiKey() {
return async (ctx: HttpContextContract, next: () => Promise) => {
const apiKey = ctx.request.header('api-key') || ctx.request.header('authorization')
if (apiKey) {
// Fail early to prevent accidental propagation of keys.
ctx.response.status = 400
ctx.response.body = { error: 'API keys not allowed when using mTLS' }
return
}
await next()
}
}
// In start/kernel.ts
Server.middleware.registerNamed({
forbidApiKey: () => forbidApiKey(),
})
// Apply to a route group that requires mTLS
Route.group(() => {
Route.get('/secure', 'SecureController.index').middleware('forbidApiKey')
}).prefix('api/v1')
Example 3: Forwarding requests without leaking keys
// start/middleware/forward-without-keys.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import axios from 'axios'
export default async function forwardWithoutKeys(ctx: HttpContextContract) {
// Strip sensitive headers before forwarding.
const headers = { ...ctx.request.headers() }
delete headers['api-key']
delete headers['authorization']
const response = await axios.get('https://downstream.internal/data', {
headers,
httpsAgent: ctx.request.httpsAgent // mTLS client cert is handled by the runtime.
})
ctx.response.send(response.data)
}
Example 4: Logging that excludes API keys
// start/providers/AppProvider.ts
import { logger } from '@ioc:Adonis/Core/Logger'
export default class AppProvider {
constructor() {
// Ensure logs do not contain sensitive header values.
logger.processLogging = (level, message, { error, ...metadata }) => {
const safeMeta = { ...metadata }
if (safeMeta.headers) {
const { 'api-key': _, authorization: __, ...safeHeaders } = safeMeta.headers
safeMeta.headers = safeHeaders
}
logger.info(message, { error, ...safeMeta })
}
}
}
Example 5: Environment-driven key handling
// .env
NODE_ENV=production
FORWARD_API_KEY=false
// start/middleware/conditional-key-forwarding.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Env from '@ioc:Adonis/Core/Env'
export default async function conditionalKeyHandling(ctx: HttpContextContract) {
const shouldForward = Env.get('FORWARD_API_KEY', 'false') === 'true'
const headers = { ...ctx.request.headers() }
if (!shouldForward) {
delete headers['api-key']
}
// Continue processing with safe headers.
await next()
}