HIGH api key exposurehapi

Api Key Exposure in Hapi

How Api Key Exposure Manifests in Hapi

Api Key Exposure in Hapi applications typically occurs through improper handling of authentication credentials in HTTP headers, query parameters, or request bodies. The most common vulnerability pattern involves sending API keys in the Authorization header without proper validation, or worse, logging these credentials inadvertently.

In Hapi, API key exposure often manifests in route handlers where developers directly access request headers without sanitization. Consider this vulnerable pattern:

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

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  server.route({
    method: 'GET',
    path: '/api/data',
    handler: (request, h) => {
      const apiKey = request.headers.authorization; // Exposed in logs
      
      // Vulnerable: apiKey logged without sanitization
      console.log(`Processing request with API key: ${apiKey}`);
      
      return { data: 'sensitive information' };
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

init().catch(err => {
  console.error(err);
  process.exit(1);
});

This code exposes the API key through server logs, which attackers can access through log aggregation services or compromised logging infrastructure. The vulnerability compounds when API keys are sent in URLs:

server.route({
  method: 'GET',
  path: '/api/data',
  handler: (request, h) => {
    const apiKey = request.query.apiKey; // Exposed in browser history, server logs
    
    // API key now appears in access logs and browser history
    return { data: 'sensitive information' };
  }
});

Another Hapi-specific manifestation occurs with route options where authentication is improperly configured:

server.route({
  method: 'POST',
  path: '/api/update',
  options: {
    auth: {
      strategy: 'simple',
      mode: 'required'
    }
  },
  handler: (request, h) => {
    // If auth fails, request object still contains partial header data
    const authHeader = request.headers.authorization;
    
    // Potential exposure if error handling logs raw headers
    if (!authHeader) {
      console.error(`Auth failed: ${JSON.stringify(request.headers)}`);
      return h.response('Unauthorized').code(401);
    }
    
    return { success: true };
  }
});

The vulnerability extends to plugin development where Hapi's plugin system can inadvertently expose credentials through plugin options or internal state sharing between plugins.

Hapi-Specific Detection

Detecting API key exposure in Hapi applications requires examining both code patterns and runtime behavior. Start with static analysis of your route definitions and handler implementations.

Code-level detection focuses on identifying dangerous patterns:

// Dangerous pattern: direct header access without validation
const apiKey = request.headers['x-api-key'];

// Dangerous pattern: logging sensitive headers
console.log(`Request from ${request.info.remoteAddress} with key: ${request.headers.authorization}`);

// Dangerous pattern: query parameter API keys
const apiKey = request.query.api_key;

// Dangerous pattern: exposing in response objects
return { apiKey: request.headers.authorization };

Runtime detection involves monitoring what gets logged and transmitted. Hapi's request lifecycle provides hooks for inspection:

server.ext('onRequest', (request, h) => {
  const authHeader = request.headers.authorization;
  
  // Detect potential exposure
  if (authHeader && authHeader.length > 50) {
    console.warn('Suspicious authorization header detected');
  }
  
  return h.continue;
});

// Log sanitization extension
server.ext('onLog', (event, h) => {
  if (event.tags.includes('request')) {
    // Remove sensitive headers from log events
    const sanitized = { ...event.data };
    delete sanitized.headers;
    return h.continue;
  }
  return h.continue;
});

For comprehensive detection, use middleBrick's API security scanner which specifically tests for API key exposure patterns in Hapi applications. The scanner examines:

  • Authorization header handling and validation
  • Query parameter authentication
  • Header logging and data exposure
  • Response object construction that might leak credentials
  • Plugin configuration for authentication

middleBrick's scanning process for Hapi applications takes 5-15 seconds and provides a security risk score with specific findings about API key exposure vulnerabilities. The scanner tests unauthenticated attack surfaces and identifies where API keys might be exposed through improper implementation patterns.

Hapi-Specific Remediation

Remediating API key exposure in Hapi requires implementing proper authentication validation, header sanitization, and secure logging practices. Start with robust authentication validation:

const Hapi = require('@hapi/hapi');
const Boom = require('@hapi/boom');

const validateApiKey = async (apiKey) => {
  // Implement proper validation logic
  // This should check against a secure store, not hardcoded values
  const validKeys = ['valid-api-key-123']; // In production, use database or secret manager
  return validKeys.includes(apiKey);
};

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  // Secure authentication scheme
server.auth.scheme('api_key', (server, options) => ({
  authenticate: async (request, h) => {
    const authHeader = request.headers.authorization;
    
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      throw Boom.unauthorized('Missing or invalid API key format');
    }
    
    const apiKey = authHeader.substring(7); // Remove 'Bearer ' prefix
    
    try {
      const isValid = await validateApiKey(apiKey);
      
      if (!isValid) {
        throw Boom.unauthorized('Invalid API key');
      }
      
      // Authenticated successfully
      return h.authenticated({
        credentials: { apiKey },
        artifacts: { apiKey }
      });
    } catch (err) {
      throw Boom.badImplementation('Authentication validation failed', err);
    }
  }
}));

  // Register authentication strategy
server.auth.strategy('api_key', 'api_key');

server.route({
  method: 'GET',
  path: '/api/data',
  options: {
    auth: {
      strategy: 'api_key',
      mode: 'required'
    }
  },
  handler: (request, h) => {
    // At this point, authentication is validated
    // No need to access raw headers
    return { data: 'secure information' };
  }
});

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

init().catch(err => {
  console.error(err);
  process.exit(1);
});

Implement secure logging with header sanitization:

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

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  // Custom request lifecycle extension for secure logging
server.ext('onRequest', (request, h) => {
  // Create a sanitized copy of headers for logging
  const sanitizedHeaders = { ...request.headers };
  
  // Remove or mask sensitive headers
  const sensitiveHeaders = ['authorization', 'x-api-key', 'api-key'];
  sensitiveHeaders.forEach(header => {
    if (sanitizedHeaders[header]) {
      sanitizedHeaders[header] = 'REDACTED';
    }
  });
  
  // Store sanitized headers for later use
  request.app.sanitizedHeaders = sanitizedHeaders;
  
  return h.continue;
});

  // Secure error handling
server.ext('onPreResponse', (request, h) => {
  const response = request.response;
  
  if (response.isBoom && response.output.statusCode >= 400) {
    // Log errors without sensitive data
    const { sanitizedHeaders } = request.app || {};
    const logData = {
      method: request.method,
      path: request.path,
      statusCode: response.output.statusCode,
      headers: sanitizedHeaders || 'unavailable'
    };
    
    request.logger.error('Request error', logData);
  }
  
  return h.continue;
});

  // Route with secure error handling
server.route({
    method: 'POST',
    path: '/api/update',
    options: {
      auth: {
        strategy: 'api_key',
        mode: 'required'
      }
    },
    handler: (request, h) => {
      try {
        // Process request securely
        return { success: true };
      } catch (err) {
        // Error handling without exposing credentials
        request.logger.error('Handler error', {
          error: err.message,
          method: request.method,
          path: request.path
        });
        
        return Boom.badImplementation('Internal server error');
      }
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

init().catch(err => {
  console.error(err);
  process.exit(1);
});

Additional remediation includes implementing rate limiting to prevent credential brute force attacks:

const Hapi = require('@hapi/hapi');
const RateLimit = require('hapi-rate-limit');

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  // Configure rate limiting
  await server.register({
    plugin: RateLimit,
    options: {
      userLimit: 100, // 100 requests per user
      userCache: {
        expiresIn: 60 * 60 * 1000, // 1 hour
        segment: 'user-auth'
      }
    }
  });

  // ... rest of server setup
};

Frequently Asked Questions

How can I test my Hapi application for API key exposure vulnerabilities?
Use middleBrick's API security scanner by submitting your Hapi application's URL. The scanner tests for API key exposure in 5-15 seconds, checking authentication handling, header logging, and response construction. It provides a security risk score (A-F) with specific findings about where API keys might be exposed in your implementation.
What's the difference between API key exposure and other authentication vulnerabilities in Hapi?
API key exposure specifically involves credentials being visible in logs, URLs, or responses, while other authentication vulnerabilities might involve weak validation or missing authentication entirely. API key exposure is particularly dangerous because even with proper authentication, exposed keys can be captured and reused by attackers through log access or network monitoring.