Integrity Failures in Adonisjs
How Integrity Failures Manifests in Adonisjs
Integrity failures in Adonisjs applications typically occur when the server accepts modified client-side data without proper validation. In Adonisjs, this often manifests through model binding, query parameters, and form submissions where users can manipulate values to escalate privileges or access unauthorized resources.
The most common Adonisjs-specific integrity failure occurs with model binding. When using route model binding like async update({ params, request }) { const user = await User.findOrFail(params.id); }, if you directly use the bound model without verifying the authenticated user's permissions, an attacker can modify the URL parameter to access any user record.
Another frequent pattern involves query parameter manipulation. Adonisjs applications often use request.input() or request.qs() to capture filters or pagination parameters. Without validation, attackers can inject values that bypass authorization checks or cause logic errors.
async index({ request }) {
const page = request.input('page', 1);
const limit = request.input('limit', 10);
// Integrity failure: unvalidated user input used directly
return await User.query()
.paginate(page, limit)
.then(({ data }) => data);
}This code allows an attacker to request any page or set an arbitrary limit, potentially causing performance issues or bypassing pagination-based access controls.
Adonisjs's Lucid ORM can also introduce integrity failures when using fetchOrCreate or updateOrCreate methods with user-supplied data. If the unique constraint or matching criteria comes from request parameters, attackers can manipulate these to create or update unintended records.
// Integrity failure: user can manipulate email to update any record
const user = await User.updateOrCreate(
{ email: request.input('email') },
{ name: request.input('name') }
);The framework's middleware system can compound these issues. If authentication middleware sets user data on the request object, and subsequent middleware or controllers trust this data without re-validation, an attacker who bypasses authentication (or manipulates the request in transit) can inject arbitrary values.
Adonisjs-Specific Detection
Detecting integrity failures in Adonisjs requires examining both the application code and runtime behavior. Static analysis should focus on identifying where user input flows into database operations or authorization decisions without validation.
Code patterns to scan for include:
- Route model binding without permission checks
- Direct use of
request.input(),request.qs(), orrequest.all()in database queries - Use of
fetchOrCreateorupdateOrCreatewith request data - Middleware that trusts request data without validation
- ACL or policy checks that use unvalidated parameters
middleBrick's black-box scanning approach is particularly effective for detecting integrity failures in Adonisjs applications. The scanner tests unauthenticated endpoints by modifying request parameters and observing how the application responds.
For example, middleBrick would test if an endpoint like /api/users/:id properly validates that the authenticated user owns the resource being accessed. It does this by:
- Scanning the endpoint without authentication to see if it returns data
- Modifying the ID parameter to test for IDOR (Insecure Direct Object Reference)
- Testing if query parameters can be manipulated to bypass filters
- Checking if pagination or limit parameters are properly constrained
The scanner's OpenAPI analysis also helps detect integrity failures by examining the API specification. If the spec shows endpoints that accept IDs or user-specific data without proper authentication requirements, this flags potential integrity issues.
middleBrick's 12 security checks include specific tests for authorization bypass attempts, making it effective at catching Adonisjs-specific integrity failures that might be missed by generic scanners.
Adonisjs-Specific Remediation
Remediating integrity failures in Adonisjs requires a defense-in-depth approach using the framework's built-in features. The primary strategy is to validate and sanitize all user input before it reaches business logic.
For route model binding, always add explicit permission checks:
async update({ params, request, auth }) {
const user = await User.findOrFail(params.id);
// Integrity fix: validate user owns this resource
if (user.id !== auth.user.id && !auth.user.isAdmin) {
return response.status(403).send('Unauthorized')
}
user.merge(request.post());
await user.save();
return user;
}Adonisjs's Validator makes input validation straightforward. Always validate pagination and filter parameters:
async index({ request }) {
const { page, limit } = await request.validate({
schema: {
page: schema.number.optional([
rules.unsigned(),
rules.range(1, 1000)
]),
limit: schema.number.optional([
rules.unsigned(),
rules.range(1, 100)
])
}
});
return await User.query()
.paginate(page || 1, limit || 20)
.then(({ data }) => data);
}For methods like updateOrCreate, validate the matching criteria:
async upsertUser({ request }) {
const { email, name } = await request.validate({
schema: {
email: schema.string({}, [
rules.email(),
rules.max(255)
]),
name: schema.string.optional([rules.max(255)])
}
});
// Only allow updating own record or if admin
const user = await User.query()
.where('email', email)
.where((builder) => {
if (!auth.user.isAdmin) {
builder.where('id', auth.user.id)
}
})
.first();
if (user) {
user.name = name;
await user.save();
} else {
await User.create({ email, name });
}
return { success: true };
}Adonisjs's policies provide a centralized way to handle authorization. Create a policy for your models:
class UserPolicy {
constructor(user) {
this.user = user
}
canView(otherUser) {
return this.user.id === otherUser.id || this.user.isAdmin
}
canUpdate(otherUser) {
return this.user.id === otherUser.id || this.user.isAdmin
}
}
// In your controller
async show({ params }) {
const user = await User.findOrFail(params.id);
const policy = new UserPolicy(auth.user);
if (!policy.canView(user)) {
return response.status(403).send('Unauthorized');
}
return user;
}For middleware that processes request data, add validation layers:
class ValidateRequestData {
async handle({ request }, next) {
const data = await request.validate({
schema: schema.object({
role: schema.string.optional([
rules.in(['user', 'admin', 'moderator'])
])
})
});
request.cleanedData = data;
await next();
}
}