Email Injection in Adonisjs with Jwt Tokens
Email Injection in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Email injection in AdonisJS typically occurs when user-controlled input is concatenated directly into email headers or commands passed to a mail driver without validation or sanitization. When JWT tokens are used for authentication and contain user-derived claims (such as email or name), developers may inadvertently use those claims in mail-related logic without treating them as untrusted input. This creates a mismatch where authentication data is treated as trusted internal data, while still originating from an external source encoded in the token.
Consider a scenario where an API endpoint authenticates requests via JWT and then uses the email claim from the token to build message headers or to select a user record for notification. If the endpoint also accepts user input (e.g., a destination email or a message body) and merges it with the token-derived email in an unsafe way, an attacker can supply crafted content such as newline characters (\n) or additional headers (e.g., Cc:, Bcc:, or custom headers). Because the application does not strictly separate control data from message content, the injected lines alter the semantics of the email, enabling header manipulation, redirection, or unintended recipient inclusion.
JWT tokens themselves do not introduce injection; the risk arises when token payloads are bound to downstream systems that interpret newlines or special characters as structural delimiters. AdonisJS applications often use the auth module to validate and retrieve claims, but if the developer writes code like message.to(auth.user.email) while also allowing a request parameter to influence recipients or headers, the token value is effectively treated as safe user-controlled data. Attackers can exploit this by obtaining or forging a token with a maliciously crafted email claim (e.g., [email protected]\nCc: [email protected]) and observe whether the injected header is reflected in the outbound message. Because the validation layer focused on token integrity (signature and expiry) and omitted output encoding or header sanitization, the vulnerability remains hidden during routine checks.
In a black-box context, an API scanner can detect indicators of this pattern by identifying endpoints that accept external input and reference JWT-derived email claims in mail-sending code paths. Because the scanning methodology runs unauthenticated tests against the exposed attack surface, it can probe parameter injection points without prior credentials. Findings typically highlight missing input validation and improper handling of user-supplied content merged with authentication data, aligning with the broader Email Injection check under the 12 security checks. Remediation focuses on treating all data flowing into mail workflows as untrusted, regardless of its origin, and applying strict allowlists and encoding before the data reaches the mail driver.
Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes
To mitigate email injection when using JWT tokens in AdonisJS, treat claims as untrusted input, validate and sanitize before use, and avoid direct concatenation into email headers or commands. Below are concrete code examples demonstrating secure handling patterns.
1. Validate and sanitize the email claim
Use an allowlist-based email validator and normalize the value before any mail-related operations. Do not trust the token payload implicitly.
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
const emailSchema = schema.create({
email: schema.string({}, [rules.email()]),
})
export default class AuthController {
public async callback({ auth, request, response }: HttpContextContract) {
const payload = await auth.use('api').verify(request)
// Explicitly validate and sanitize the claim
const validated = await request.validate({ schema: emailSchema })
const safeEmail = validated.email.trim().toLowerCase()
// Use safeEmail for mail operations, not payload.email directly
await Mail.sendLater((message) => {
message
.to(safeEmail)
.subject('Welcome')
.htmlView('emails/welcome', { userEmail: safeEmail })
})
return response.ok({ email: safeEmail })
}
}
2. Avoid injecting user data into headers
Never merge request parameters or token claims directly into header construction. Use explicit recipient lists and avoid dynamic header names.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Mail from '@ioc:Adonis/Addons/Mail'
export default class NotificationController {
public async send({ request, auth }: HttpContextContract) {
const { subject, body } = request.only(['subject', 'body'])
const userEmail = auth.user?.email
if (!userEmail) {
throw new Error('Unauthenticated')
}
// Safe: fixed recipient, no dynamic headers
await Mail.sendLater((message) => {
message
.to(userEmail)
.subject(subject)
.textView('emails/text', { body })
// Do not set headers from user input
})
}
}
3. Sanitize any external input merged with token-derived data
If an endpoint accepts a destination email (e.g., for invitation flows), validate it separately and do not concatenate or interpolate it into header strings.
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Mail from '@ioc:Adonis/Addons/Mail'
const inviteSchema = schema.create({
to: schema.string({}, [rules.email()]),
message: schema.string.optional(),
})
export default class InviteController {
public async invite({ request, auth }: HttpContextContract) {
const payload = await auth.use('api').verify(request)
const { to, message } = await request.validate({ schema: inviteSchema })
// Safe: validated email used directly; no injection-prone concatenation
await Mail.sendLater((msg) => {
msg
.to(to)
.from(payload.email || '[email protected]')
.subject('You are invited')
.htmlView('emails/invite', { body: message || '' })
})
}
}
These patterns ensure JWT claims and external input are validated and encoded, reducing the risk of email injection. They align with secure coding practices by avoiding implicit trust in authentication-derived data and enforcing strict separation between control information and message content.