Header Injection in Adonisjs with Mongodb
Header Injection in Adonisjs with Mongodb — how this specific combination creates or exposes the vulnerability
Header injection in an Adonisjs application using Mongodb typically occurs when user-controlled data is reflected into HTTP response headers without validation or sanitization. Because Adonisjs handles HTTP interactions through its Http server and response utilities, and because the app may query or store data in Mongodb, an attacker can supply header-like input that traverses from the database to the response layer.
For example, an attacker may control a profile field stored in a Mongodb collection (e.g., bio or custom_header) and later inject newline characters (CRLF) when that value is rendered into a response header by Adonisjs code. In Adonisjs, this can happen if a controller fetches a document from Mongodb and directly assigns a field to a header using response.header() or similar patterns without sanitizing newlines or disallowed characters. The CRLF sequences (\r\n) enable splitting headers, which can lead to response splitting, HTTP response smuggling, or injection of arbitrary headers such as Set-Cookie or Location.
The risk is compounded when the Mongodb document contains user-generated content that is not strictly validated before being stored and later rendered by Adonisjs. Attackers may also probe for open redirects or XSS by injecting values like Location: https://evil.example into a header-valued field stored in Mongodb. Because the application trusts stored data and reuses it in headers, the boundary between data store and protocol layer blurs, creating a pathway for injection.
Middleware that modifies or logs responses in Adonisjs can further propagate tainted values if they copy user-controlled fields into headers without canonicalization. Even if the primary API uses JSON, header injection remains viable when responses include custom headers derived from Mongodb content. The key condition is the lack of strict allow-listing and canonicalization before using any data from Mongodb in an HTTP header context within Adonisjs.
Mongodb-Specific Remediation in Adonisjs — concrete code fixes
To prevent header injection when using Mongodb with Adonisjs, treat any field that may be used in HTTP headers as untrusted input. Apply strict allow-listing, canonicalization, and encoding before assigning values to headers. Below are concrete patterns and code examples for safe handling.
1. Validate and sanitize header-valued fields on output
Never directly assign a Mongodb document field to a response header. Instead, validate and sanitize. For a field like customHeader that must be used in a header, allow only safe characters and reject or transform newline characters.
const sanitizeHeader = (value) => {
if (typeof value !== 'string') return '';
// Strip or reject CR/LF to prevent header splitting
return value.replace(/[\r\n]+/g, '');
};
const user = await User.findBy('id', params.id);
const safeHeaderValue = sanitizeHeader(user?.customHeader || '');
if (safeHeaderValue) {
response.header('X-Custom-Header', safeHeaderValue);
}
2. Use Adonisjs response helpers safely with Mongodb data
When using response.header() or constructing redirects, ensure values from Mongodb are normalized and constrained.
const userProfile = await Profile.findOne({ email: request.input('email') });
// Reject dangerous characters; only allow alphanumeric, dash, underscore, and limited punctuation
const safeLocation = userProfile?.redirectUrl
? userProfile.redirectUrl.replace(/[^a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;%=]+/g, '')
: '/dashboard';
// Use Adonisjs response redirect which does not allow header injection via Location when used safely
if (safeLocation.startsWith('/')) {
return response.redirect(safeLocation);
} else {
return response.redirect('/fallback');
}
3. Enforce schema-level constraints in Mongodb
Define schema rules in Mongodb to restrict header-relevant fields at the database level. For example, ensure that fields used in headers are alphanumeric with limited symbols and reject embedded control characters.
const mongodb = use('Database');
const userCollection = mongodb.collection('users');
// Example insert/update validation on the application side before sending to Mongodb
const validatedBio = request.input('bio').replace(/[\r\n]+/g, ' ').substring(0, 500);
await userCollection.updateOne(
{ _id: ObjectId('...') },
{ $set: { bio: validatedBio } }
);
4. Centralize header assignment via a service layer
Create a service that mediates between Mongodb and Adonisjs response headers, ensuring consistent sanitization.
class HeaderService {
static applyUserHeaders(response, userId) {
const user = User.findBy('id', userId);
if (user?.extraHeaders) {
user.extraHeaders.forEach(({ name, value }) => {
const safeName = name.replace(/[^a-zA-Z0-9-]+/g, '');
const safeValue = value.replace(/[\r\n]+/g, '');
response.header(safeName, safeValue);
});
}
}
}
// In controller
HeaderService.applyUserHeaders(response, userId);
5. Content Security and CSP headers derived from trusted sources
If you must derive CSP or other security headers from Mongodb, treat them as code-like inputs and avoid concatenating raw user strings. Prefer allow-listing directives and avoid injecting user data into directive values.
const baseCsp = "default-src 'self'";
const userExtras = await UserCsp.findOne({ userId });
// Do NOT directly append userExtras?.directives without strict parsing
const finalCsp = userExtras?.safeDirective ? `${baseCsp}; ${userExtras.safeDirective}` : baseCsp;
response.header('Content-Security-Policy', finalCsp);
6. Testing and monitoring
Include tests that verify header values never contain CRLF when sourced from Mongodb. Use Adonisjs tests to assert that response headers are correctly sanitized and that attempts to inject newlines are neutralized.
test('header values from mongodb are sanitized', async () => {
const user = await User.create({ customHeader: 'value\r\nInjected: bad' });
const response = await request.get('/profile').query({ id: user.id });
response.assertStatus(200);
assert(!response.headers['x-custom-header'].includes('\r'));
assert(!response.headers['x-custom-header'].includes('\n'));
});