Use After Free in Adonisjs with Api Keys
Use After Free in Adonisjs with Api Keys — how this combination creates or exposes the vulnerability
Use After Free (UAF) in AdonisJS when combined with API key handling can occur when an in-memory object such as a key payload or parsed token is deallocated or made unavailable while a subsequent operation still holds a reference to it. In API key flows this typically surfaces in middleware or route handlers that cache or reuse key material across requests. For example if you parse an API key once and store a reference on the request object for later stages (validation scopes permissions), but then the key object is recycled or overwritten before the later stage finishes you may read stale data or trigger undefined behavior.
Consider a middleware that resolves an API key and attaches a user object to ctx.state.user without copying values deeply:
// middleware/resolveApiKey.js
const ApiKey = use('App/Models/ApiKey')
async function resolveApiKey ({ request, response, next }) {
const key = request.header('x-api-key')
if (!key) {
return response.unauthorized('missing key')
}
const record = await ApiKey.query().where('key', key).first()
if (!record) {
return response.unauthorized('invalid key')
}
// Attaching the model instance directly can expose reuse risks under certain runtime conditions
request.user = record.user
await next()
}
module.exports = { resolveApiKey }
If the runtime recycles the record or user object between hooks and another handler later uses that reference after it has been released you may observe Use After Free style effects such as corrupted state or information leaks. This pattern becomes riskier when you combine long lived references with asynchronous gaps where the object lifecycle is managed by the framework or underlying runtime rather than explicitly controlled by your code.
Another scenario involves request scoped caching of key material. Suppose you normalize and cache the key payload in memory to avoid repeated database hits:
// services/keyCache.js
const cache = new Map()
function getKeyPayload (rawKey) {
if (!cache.has(rawKey)) {
// Simulated heavy parsing that produces an object kept alive by cache
const payload = { scope: 'read:data', userId: 123 }
cache.set(rawKey, payload)
}
return cache.get(rawKey)
}
If the cache eviction or replacement logic is tied to memory pressure or request frequency and a handler retains a reference to an old payload beyond the intended scope that payload may be overwritten while still in use. This is effectively Use After Free because the handler operates on memory that has been logically freed or reassigned. In AdonisJS you mitigate this by avoiding direct attachment of shared or reused objects and by ensuring key handling code does not keep references across asynchronous boundaries without isolation.
LLM/AI Security checks in middleBrick specifically look for system prompt leakage and prompt injection patterns but they do not detect Use After Free. For API key flows you should complement middleBrick scans with code review focused on object lifecycle discipline especially when using caches or attaching user data to request state. The goal is to ensure key material is copied or isolated where necessary and not left in a state where it can be overwritten while still referenced.
Api Keys-Specific Remediation in Adonisjs — concrete code fixes
Remediation centers on strict lifecycle control and isolation of API key material. Avoid mutating or reusing objects that hold key derived data across awaits and do not attach raw model instances directly to the request when those instances may be managed by an ORM pool or cache.
1) Copy data instead of sharing references. Create a plain object with only the fields you need rather than attaching the full model instance:
// middleware/resolveApiKey.js (fixed)
const ApiKey = use('App/Models/ApiKey')
async function resolveApiKey ({ request, response, next }) {
const key = request.header('x-api-key')
if (!key) {
return response.unauthorized('missing key')
}
const record = await ApiKey.query().where('key', key).first()
if (!record) {
return response.unauthorized('invalid key')
}
// Isolate key material into a plain object
request.apiKeyInfo = {
userId: record.userId,
scope: record.scope,
keyId: record.id
}
await next()
}
module.exports = { resolveApiKey }
2) Use request-scoped storage rather than a global cache for key payloads. If you must cache prefer short lived structures that are keyed by request identifier and cleared at the end of the request lifecycle:
// services/keyCache.js (request-aware version)
class RequestScopedCache {
constructor () {
this.store = new WeakMap()
}
setForRequest (req, value) {
this.store.set(req, value)
}
getForRequest (req) {
return this.store.get(req)
}
}
const keyCache = new RequestScopedCache()
function getKeyPayloadForRequest (req, rawKey) {
const existing = keyCache.getForRequest(req)
if (existing && existing.raw === rawKey) {
return existing.payload
}
// Parse and store scoped payload for this request only
const payload = { scope: 'read:data', userId: 123 }
keyCache.setForRequest(req, { raw: rawKey, payload })
return payload
}
3) Validate and scope check on every sensitive operation rather than relying on early attached state. This ensures that even if earlier state is compromised later checks recompute authorization:
// policies/apiKeyPolicy.js
async function canReadData ({ params, request }) {
const keyHeader = request.header('x-api-key')
if (!keyHeader) {
return false
}
// Re fetch or validate scope for the operation
const hasScope = await verifyScope(keyHeader, 'read:data')
return hasScope
}
4) In the GitHub Action add API security checks to your CI/CD pipeline and set a quality gate so that builds fail if risk scores exceed your threshold. This prevents merging code patterns that reintroduce unsafe key handling:
# .github/workflows/api-security.yml
name: API Security Check
on: [push]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run middleBrick scan
run: npx middlebrick scan ${{ secrets.API_ENDPOINT }} --threshold B
These steps reduce the chance of Use After Free by eliminating shared mutable references and ensuring key material is handled in an isolated and request bounded manner.