Type Confusion in Adonisjs
How Type Confusion Manifests in Adonisjs
Type confusion in Adonisjs occurs when the framework's type coercion and dynamic property access mechanisms allow attackers to manipulate input data types, causing the application to misinterpret values and execute unintended logic paths. This vulnerability is particularly dangerous in Adonisjs because of its flexible request handling and schema validation system.
The most common Adonisjs-specific manifestation involves the framework's request body parsing and schema validation. When Adonisjs processes incoming requests, it automatically parses JSON payloads and assigns them to the request object. If an endpoint expects a specific type but receives a different one, the framework's coercion rules can lead to unexpected behavior.
class UserController {
async update({ request, response }) {
const data = request.post()
// Expects a number but receives a string
const userId = data.id
const user = await User.find(userId)
// Type confusion: string '0' becomes falsy, skipping authorization
if (!user || user.id === 0) {
return response.notFound()
}
// Further processing with potentially wrong user object
}
}Another Adonisjs-specific scenario involves Lucid model operations where type confusion can lead to BOLA (Broken Object Level Authorization) vulnerabilities. When using dynamic where clauses with user-provided values, incorrect type handling can bypass authorization checks.
class ProfileController {
async updateProfile({ request, response, auth }) {
const data = request.post()
const profileId = data.profile_id
// Type confusion vulnerability
const profile = await Profile.query()
.where('id', profileId)
.where('user_id', auth.user.id)
.first()
// If profileId is '0' or falsy string, query returns first profile
// regardless of user_id, bypassing authorization
if (!profile) {
return response.notFound()
}
await profile.merge(data).save()
return response.ok(profile)
}
}Adonisjs's schema validation system can also introduce type confusion when using loose validation rules. The framework's ability to coerce types during validation can lead to situations where validated data doesn't match the expected runtime types.
class UpdateUserValidator {
get rules() {
return {
age: 'number|min:0', // Accepts numeric strings
email: 'email',
isAdmin: 'boolean' // Accepts 'true'/'false' strings
}
}
}
// Validator usage
class UserController {
async update({ request, response }) {
const data = await request.validate(UpdateUserValidator)
// data.age is now a number, but what if validation failed?
// Type confusion can occur if validation rules are too permissive
const user = await User.find(data.id)
// isAdmin might be a string 'true' instead of boolean true
if (data.isAdmin === true) {
user.role = 'admin'
}
await user.save()
return response.ok(user)
}
}Adonisjs-Specific Detection
Detecting type confusion in Adonisjs applications requires examining both the request handling patterns and the validation logic. middleBrick's API security scanner includes specific checks for Adonisjs applications that analyze runtime behavior and schema validation patterns.
middleBrick scans Adonisjs endpoints by sending crafted payloads with type variations to identify where the application's type coercion creates security gaps. The scanner tests for common Adonisjs patterns like numeric string injection, boolean string manipulation, and falsy value exploitation.
# Using middleBrick CLI to scan Adonisjs API
npm install -g middlebrick
middlebrick scan https://api.example.com/users --target adonisjs
# Or integrate into Adonisjs project
middlebrick scan http://localhost:3333 --watch
The scanner specifically looks for Adonisjs-specific vulnerabilities including:
- Request body type coercion in route handlers
- Loose schema validation rules that accept multiple types
- Dynamic query building with user-controlled parameters
- Model finder methods with type-ambiguous inputs
middleBrick's LLM security features also detect if your Adonisjs application serves AI endpoints that might be vulnerable to prompt injection through type confusion in AI model parameters.
For manual detection in Adonisjs applications, examine these patterns:
// Check for loose validation rules
class LooseValidator {
get rules() {
return {
id: 'number', // Should be 'integer' or 'range'
status: 'boolean', // Should validate boolean specifically
amount: 'number' // Missing min/max constraints
}
}
}
// Check for unsafe dynamic queries
class UnsafeController {
async find({ request }) {
const id = request.input('id', 0)
// Type confusion risk: id could be string, array, or object
const user = await User.find(id)
return user
}
}middleBrick's continuous monitoring (Pro plan) can automatically rescan your Adonisjs APIs on a schedule, alerting you when new type confusion vulnerabilities are introduced through code changes.
Adonisjs-Specific Remediation
Remediating type confusion in Adonisjs requires strict type validation and careful handling of user inputs. Adonisjs provides several native features to prevent type confusion vulnerabilities.
First, use strict validation rules in your validators. Adonisjs's schema validation system allows you to specify exact types and reject ambiguous inputs.
class StrictValidator {
get rules() {
return {
id: 'integer|required', // Rejects numeric strings
age: 'integer|min:0|max:150', // Strict integer validation
email: 'email|required',
isActive: 'boolean|required' // Only true/false accepted
}
}
// Custom sanitization for additional safety
get sanitizationRules() {
return {
id: 'toInt',
age: 'toInt'
}
}
}
// Usage in controller
class UserController {
async update({ request, response }) {
try {
const data = await request.validate(StrictValidator)
// Now data.id is guaranteed to be an integer
const user = await User.find(data.id)
if (!user) {
return response.notFound()
}
await user.merge(data).save()
return response.ok(user)
} catch (error) {
return response.badRequest({
message: 'Invalid input data',
details: error.messages
})
}
}
}Second, implement explicit type checking in your route handlers. Adonisjs's TypeScript support helps catch many type issues at compile time, but runtime validation is still necessary.
class SafeController {
async show({ request, response }) {
const id = request.input('id')
// Explicit type validation
if (typeof id !== 'number' && !Number.isInteger(Number(id))) {
return response.badRequest({ error: 'ID must be an integer' })
}
const userId = Number(id)
const user = await User.find(userId)
if (!user) {
return response.notFound()
}
return response.ok(user)
}
async create({ request, response }) {
const data = request.post()
// Validate object structure before processing
if (!data || typeof data !== 'object') {
return response.badRequest({ error: 'Invalid payload' })
}
// Check for unexpected properties
const allowedKeys = ['name', 'email', 'age']
const unknownKeys = Object.keys(data).filter(
key => !allowedKeys.includes(key)
)
if (unknownKeys.length > 0) {
return response.badRequest({
error: 'Unexpected properties',
keys: unknownKeys
})
}
// Safe to process validated data
const user = await User.create(data)
return response.created(user)
}
}Third, use Adonisjs's query builder safely by avoiding dynamic type coercion in where clauses.
class SecureProfileController {
async updateProfile({ request, response, auth }) {
const data = request.post()
// Explicit validation of profile ID
const profileId = data.profile_id
if (typeof profileId !== 'number' && !Number.isInteger(Number(profileId))) {
return response.badRequest({ error: 'Invalid profile ID' })
}
const userId = auth.user.id
// Use explicit type conversion and validation
const profile = await Profile.query()
.where('id', Number(profileId))
.where('user_id', userId)
.first()
if (!profile) {
return response.notFound()
}
await profile.merge(data).save()
return response.ok(profile)
}
}
// For boolean parameters, use explicit parsing
class FeatureController {
async toggleFeature({ request, response }) {
const featureId = request.input('feature_id')
const enabled = request.input('enabled')
// Strict boolean parsing
const isEnabled = enabled === 'true' || enabled === true || enabled === '1'
const isDisabled = enabled === 'false' || enabled === false || enabled === '0'
if (!isEnabled && !isDisabled) {
return response.badRequest({ error: 'Enabled must be boolean' })
}
const feature = await Feature.find(featureId)
feature.enabled = isEnabled
await feature.save()
return response.ok(feature)
}
}Finally, implement comprehensive error handling to prevent type confusion from causing information disclosure or unexpected application states.
// Global error handler for type-related issues
class TypeConfusionHandler {
async handle(error, { response }) {
if (error.code === 'E_VALIDATION_FAILURE') {
return response.badRequest({
message: 'Validation failed',
errors: error.messages
})
}
if (error instanceof TypeError) {
// Log the error but don't expose internal details
console.error('Type error:', error)
return response.internalServerError({
message: 'Internal server error'
})
}
return response.internalServerError()
}
}
// Register in start/events.js
Event.on('error', async (error, { response }) => {
const handler = new TypeConfusionHandler()
await handler.handle(error, { response })
})Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |