HIGH command injectionhapiapi keys

Command Injection in Hapi with Api Keys

Command Injection in Hapi with Api Keys — how this specific combination creates or exposes the vulnerability

Command injection in a Hapi API that uses API keys can occur when keys are handled in a way that introduces user-controlled data into system commands. Hapi routes often process headers, query parameters, or payload fields that an attacker may try to manipulate. If a route extracts an API key from a request and passes it to a shell command—such as through child_process functions like exec or spawn—without proper validation or escaping, the key value can become an injection vector.

Consider a scenario where an API key is logged, parsed, or forwarded to a diagnostic script. An attacker who can influence the key (e.g., via a custom header x-api-key) might supply crafted input such as validkey; cat /etc/passwd. If the server uses the key in an unsafe command like exec('grep ' + apiKey + ' /var/log/app.log'), the semicolon terminates the intended command and appends a malicious one, exposing sensitive files. This is a classic command injection pattern, and it is particularly dangerous when API keys are assumed to be opaque tokens and therefore not validated for format.

Hapi does not inherently sanitize inputs; developers are responsible for sanitization and strict validation. The risk is compounded when API keys are used in contexts where they interact with the operating system, such as invoking external utilities for key rotation, audit logging, or integration with third‑party services. An attacker who cannot directly call the Hapi endpoints due to network restrictions might still exploit a secondary route that builds commands from key material, especially if that route has weaker authentication controls.

Another subtle vector involves environment variables or configuration files that embed API keys into command templates. If a Happiness route reads a key from a header and interpolates it into a shell string that is later executed, the key can break out of its intended context. For instance, a key containing characters like backticks, $(), or escaped quotes can alter command syntax. Even seemingly benign commands like tar or curl can be weaponized when combined with uncontrolled input.

Because API keys are often long, random strings, they may include characters that are meaningful to the shell. Developers might mistakenly believe that base64‑encoded keys are safe, but padding characters (=) and the + sign can still be problematic in certain command contexts. The combination of Hapi’s flexible request handling and external command execution creates a narrow path where an API key can shift from opaque credential to executable code if the integration logic is not carefully constrained.

To detect such issues, scans analyze route definitions for external process calls and check whether request-derived data—such as headers that commonly carry API keys—flows into those calls. The scanner does not assume that keys are safe simply because they are long and random; it examines whether the application treats them as untrusted input. This helps surface command injection risks that are specific to the way API keys are consumed rather than generic injection flaws.

Api Keys-Specific Remediation in Hapi — concrete code fixes

Remediation focuses on avoiding shell interpolation entirely and enforcing strict validation on API key usage. The safest approach is to never pass API key values directly to shell commands. If external tooling is required, use controlled interfaces that do not rely on string concatenation.

Validation and allowlists

Validate API keys against a strict pattern. If keys are expected to be base64url strings, enforce that format before any further processing. This reduces the chance that a key contains shell metacharacters.

const Hapi = require('@hapi/hapi');
const validateApiKey = (key) => /^[A-Za-z0-9\-_]+$/.test(key);

const server = Hapi.server({ port: 4000, host: 'localhost' });

server.route({
  method: 'POST',
  path: '/log',
  options: {
    validate: {
      headers: Joi.object({
        'x-api-key': Joi.string().custom((value, helpers) => {
          if (!validateApiKey(value)) {
            return helpers.error('any.invalid');
          }
          return value;
        }).required()
      }).unknown()
    },
    handler: (request, h) => {
      const apiKey = request.headers['x-api-key'];
      // Safe: key is validated, but do not use in shell commands
      return h.response({ received: true }).code(200);
    }
  }
});

await server.start();

Avoid shell execution; use native modules

Instead of shelling out to grep or awk, use Node.js native modules to handle log parsing or data operations. This eliminates shell injection risk entirely.

const fs = require('fs');
const Hapi = require('@hapi/hapi');

const server = Hapi.server({ port: 4000, host: 'localhost' });

server.route({
  method: 'GET',
  path: '/search-log',
  options: {
    validate: {
      headers: Joi.object({
        'x-api-key': Joi.string().pattern(/^[A-Za-z0-9\-_]+$/).required()
      }).unknown()
    },
    handler: (request, h) => {
      const apiKey = request.headers['x-api-key'];
      const data = fs.readFileSync('/var/log/app.log', 'utf8');
      const lines = data.split('\n');
      // Filter using JavaScript, not shell commands
      const matches = lines.filter(line => line.includes(apiKey));
      return { matches };
    }
  }
});

await server.start();

Secure external tooling (if unavoidable)

If you must invoke an external binary, use fixed command paths and pass API keys as environment variables or via stdin, never as command-line arguments. Prefer child_process.spawn with an argument array to avoid shell parsing.

const { spawn } = require('child_process');
const Hapi = require('@hapi/hapi');

const server = Hapi.server({ port: 4000, host: 'localhost' });

server.route({
  method: 'POST',
  path: '/external',
  options: {
    validate: {
      headers: Joi.object({
        'x-api-key': Joi.string().pattern(/^[A-Za-z0-9\-_]+$/).required()
      }).unknown()
    },
    handler: (request, h) => {
      const apiKey = request.headers['x-api-key'];
      // Safe: arguments passed as array, no shell interpolation
      const child = spawn('/usr/local/bin/processor', ['--key', apiKey]);
      let stdout = '';
      child.stdout.on('data', (data) => { stdout += data; });
      return new Promise((resolve) => {
        child.on('close', () => resolve({ output: stdout }));
      });
    }
  }
});

await server.start();

These patterns demonstrate how to handle API keys safely in Hapi: strict validation, avoiding shell commands, and using native code paths. They align with the remediation guidance provided in scan reports and help prevent command injection while preserving the intended functionality.

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

Why are API keys not considered safe even if they look random?
API keys can contain shell-meaningful characters such as semicolons, quotes, backticks, or operators that may be interpreted by a shell if passed to command construction. Randomness does not guarantee safety; validation and avoiding shell interpolation are required.
Can middleware or plugins fully prevent command injection in Hapi?
Middleware can enforce validation policies, but it does not automatically prevent command injection. Developers must avoid passing untrusted data—including header values like API keys—to shell commands and use native modules or controlled process invocation instead.