Insecure Design in Adonisjs
How Insecure Design Manifests in Adonisjs
Insecure design in Adonisjs applications often stems from architectural decisions that expose sensitive functionality or create privilege escalation paths. Unlike injection or authentication flaws, these issues arise from how the application is structured rather than implementation bugs.
One common pattern involves exposing administrative endpoints without proper access controls. Consider an Adonisjs controller that handles user management:
class UserController {
async destroy ({ params, auth }) {
const user = await User.find(params.id)
await user.delete()
return { success: true }
}
}This endpoint allows any authenticated user to delete any other user by simply changing the ID parameter—a classic Broken Object Level Authorization (BOLA) vulnerability. The design assumes the API consumer will only access their own resources, but the implementation provides no enforcement.
Another frequent issue involves improper data exposure through API responses. Adonisjs developers might inadvertently return sensitive fields:
class UserController {
async show ({ params }) {
const user = await User.query().where('id', params.id).with('profile').first()
return user
}
}If the User model includes fields like password_hash, api_key, or ssn, these get serialized and sent to the client. Adonisjs's Lucid ORM automatically serializes all model attributes unless explicitly excluded.
Rate limiting design flaws also plague Adonisjs applications. Without proper throttling on sensitive endpoints like login or password reset, applications become vulnerable to credential stuffing attacks:
Route.post('login', 'AuthController.login')
Route.post('reset-password', 'AuthController.resetPassword')These routes lack any rate limiting middleware, allowing attackers to brute force credentials indefinitely.
Property authorization issues arise when Adonisjs applications fail to enforce field-level access controls. For example, an e-commerce API might allow any user to view inventory costs:
class ProductController {
async index ({ request }) {
const products = await Product.all()
return products
}
}Sales personnel and customers receive identical data, including wholesale costs and supplier information that should be restricted.
Adonisjs-Specific Detection
Detecting insecure design in Adonisjs requires examining both the codebase structure and runtime behavior. Start by analyzing your route definitions and controller patterns:
const routes = use('Route')
routes.get('users/:id', 'UserController.show')
routes.delete('users/:id', 'UserController.destroy')
routes.post('admin/:action', 'AdminController.handleAction')Look for patterns where ID parameters appear in routes without corresponding authorization checks. The /:id pattern is particularly suspicious when it appears in DELETE, PUT, or PATCH routes.
Model serialization is another critical area. Adonisjs models include all attributes by default:
class User extends Model {
static get traits () {
return ['Adonis/Lucid/SoftDeletes']
}
static get hidden () {
return ['password_hash', 'api_key', 'ssn']
}
}Check if sensitive fields are properly hidden using the hidden property. Missing exclusions here indicate insecure design.
Middleware usage patterns reveal authorization design flaws. Examine your start/kernel.js file:
const globalMiddleware = [
'Adonis/Middleware/Cors',
'Adonis/Middleware/BodyParser',
'App/Middleware/Auth'
]Is there a global authentication middleware? Missing authentication at the route level suggests endpoints might be publicly accessible when they shouldn't be.
Automated scanning with middleBrick provides comprehensive detection of these design issues. The scanner analyzes your Adonisjs API endpoints for:
- BOLA vulnerabilities by testing IDOR patterns across authenticated and unauthenticated requests
- Data exposure by examining response payloads for sensitive information
- Missing authentication on sensitive endpoints
- Inadequate authorization controls
- Rate limiting gaps on authentication and sensitive operations
middleBrick's black-box scanning approach tests the actual API surface without requiring source code access, making it ideal for detecting design flaws that manifest at runtime.
Adonisjs-Specific Remediation
Remediating insecure design in Adonisjs requires architectural changes and proper use of the framework's security features. Start with robust authorization using Adonisjs's Gate and Policy system:
const Gate = use('Gate')
Gate.policy('User', (user, targetUser) => {
return user.id === targetUser.id || user.isAdmin
})
// In controller:
async destroy ({ params, auth }) {
const user = await User.find(params.id)
if (!Gate.can('delete', user)) {
return response.status(403).json({ error: 'Unauthorized' })
}
await user.delete()
return { success: true }
}This pattern ensures users can only delete their own accounts or have admin privileges for broader access.
Model serialization controls prevent data exposure:
class User extends Model {
static get hidden () {
return ['password_hash', 'api_key', 'ssn', 'credit_card']
}
static get visible () {
return ['id', 'name', 'email', 'created_at']
}
}The visible property explicitly defines what fields should be included, providing defense in depth beyond just hiding sensitive data.
Route-level middleware enforces authentication and authorization consistently:
Route.group(() => {
Route.get('users/:id', 'UserController.show').middleware(['auth', 'authorize:user'])
Route.delete('users/:id', 'UserController.destroy').middleware(['auth', 'authorize:user'])
})
.middleware('rateLimit:100')This groups related routes and applies consistent security controls. The rateLimit middleware prevents abuse of sensitive endpoints.
Property authorization requires field-level access controls. Implement resource-specific policies:
Gate.policy('Product', (user, product, action) => {
switch (action) {
case 'viewPublic':
return true
case 'viewCost':
return user.role === 'manager' || user.role === 'admin'
case 'viewSupplier':
return user.role === 'admin'
default:
return false
}
})
// Controller usage:
async show ({ params, auth }) {
const product = await Product.find(params.id)
if (!Gate.can('viewPublic', product)) {
return response.status(403).json({ error: 'Unauthorized' })
}
const fields = Gate.can('viewCost', product)
? ['id', 'name', 'price', 'cost', 'supplier']
: ['id', 'name', 'price']
return product.toJSON({ fields })
}This approach provides granular control over what data different user roles can access.
middleBrick's scanning capabilities verify these remediations by testing the actual API behavior. After implementing fixes, rescan your endpoints to ensure the security score improves and all vulnerabilities are resolved. The scanner's continuous monitoring feature (Pro plan) can alert you if new insecure design patterns emerge as your codebase evolves.