HIGH email injectionadonisjsjwt tokens

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., attacker@example.com\nCc: victim@example.com) 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 || 'noreply@example.com')
        .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.

Frequently Asked Questions

How can I test for email injection in my AdonisJS API without authenticated access?
Use an API security scanner that performs unauthenticated black-box testing, such as middleBrick. Submit the endpoint URL to detect missing input validation and improper handling of JWT-derived data merged with external input. Review reported findings and apply allowlist validation and encoding as remediation.
Does using JWT tokens make email injection more likely in AdonisJS?
JWT tokens themselves do not cause email injection. The risk occurs when claims from tokens (e.g., email) are used in mail workflows alongside untrusted request data without validation or sanitization. Properly validating and encoding all data before it reaches mail drivers mitigates the issue.