HIGH broken access controlhapi

Broken Access Control in Hapi

How Broken Access Control Manifests in Hapi

Broken Access Control in Hapi applications often emerges through subtle configuration oversights and framework-specific patterns. The most common manifestation occurs when developers rely on Hapi's default authentication state without implementing proper authorization checks. For instance, Hapi's auth.strategy configuration may authenticate users but leave route handlers vulnerable to privilege escalation.

Consider this Hapi route configuration:

server.route({
  method: 'GET',
  path: '/admin/dashboard',
  options: {
    auth: 'jwt' // Authenticates but doesn't authorize
  },
  handler: (request, h) => {
    // No role checking - any authenticated user can access
    return dashboardData;
  }
});

This pattern is particularly dangerous because Hapi will happily serve the endpoint to any authenticated user, regardless of their actual permissions. The framework's pre-handler extension points can also be misused. Developers sometimes attach authentication checks to onRequest extensions but forget to validate specific resource ownership within handlers:

server.ext('onRequest', (request, h) => {
  if (!request.auth.isAuthenticated) {
    return h.unauthenticated();
  }
  return h.continue;
});

Another Hapi-specific vulnerability pattern involves scope-based authorization that's too permissive. Hapi's scope option in route configuration can create a false sense of security:

server.route({
  method: 'PUT',
  path: '/users/{id}',
  options: {
    auth: {
      strategy: 'jwt',
      scope: ['user', 'admin'] // Any user with either scope can update
    }
  },
  handler: (request, h) => {
    const userId = request.params.id;
    // No check that userId matches authenticated user's ID
    return updateUser(userId, request.payload);
  }
});

This allows any authenticated user with the 'user' scope to update any other user's profile, creating a classic IDOR (Insecure Direct Object Reference) vulnerability. Hapi's plugin architecture can compound these issues when plugins don't properly validate user context before performing operations.

Hapi-Specific Detection

Detecting Broken Access Control in Hapi requires understanding both the framework's authentication flow and common misconfigurations. Static analysis tools often miss Hapi-specific patterns because they don't understand the framework's extension system and plugin architecture.

Dynamic scanning with middleBrick specifically targets Hapi's authentication patterns. The scanner tests authenticated endpoints by cycling through different user contexts to identify whether authorization checks are properly implemented. For Hapi applications, middleBrick's LLM/AI Security module is particularly relevant since Hapi often serves as the backend for AI-powered applications.

middleBrick's scanning process for Hapi APIs includes:

  • Authentication bypass attempts using modified tokens to test scope enforcement
  • Parameter manipulation testing for IDOR vulnerabilities in Hapi's path parameter handling
  • Scope escalation attempts by modifying JWT claims before Hapi processes them
  • Plugin boundary testing to ensure plugins don't bypass authentication
  • Pre-handler extension testing to verify that authentication checks aren't improperly short-circuited

The scanner specifically looks for Hapi's validate option in route configuration, which is often used for input validation but can be exploited for authorization bypass if not properly implemented:

server.route({
  method: 'GET',
  path: '/sensitive-data',
  options: {
    validate: {
      headers: Joi.object({
        'x-custom-token': Joi.string().required()
      }).unknown()
    }
  },
  handler: (request, h) => {
    // Missing authorization check on custom header
    return sensitiveData;
  }
});

middleBrick's black-box scanning approach is particularly effective for Hapi applications because it doesn't require access to source code or configuration files. The scanner can identify whether Hapi's default behaviors are being properly secured, such as testing whether auth: false is used appropriately or whether authentication strategies are properly scoped.

Hapi-Specific Remediation

Fixing Broken Access Control in Hapi requires leveraging the framework's built-in authorization features while avoiding common pitfalls. The most robust approach uses Hapi's pre handler system to enforce authorization before route handlers execute:

const checkResourceOwnership = (request, h) => {
  const userId = request.auth.credentials.userId;
  const resourceId = request.params.id;
  
  return checkOwnership(userId, resourceId)
    .then(isOwner => {
      if (!isOwner) {
        return h.response({ error: 'Insufficient permissions' }).code(403);
      }
      return h.continue;
    });
};

server.route({
  method: 'GET',
  path: '/users/{id}/profile',
  options: {
    auth: 'jwt',
    pre: [
      { method: checkResourceOwnership, assign: 'ownership' }
    ]
  },
  handler: (request, h) => {
    return getUserProfile(request.params.id);
  }
});

For role-based access control in Hapi, use the framework's built-in plugins['hapi-auth-scopes'] with proper validation:

const isAdmin = (request, h) => {
  const scopes = request.auth.credentials.scope || [];
  if (!scopes.includes('admin')) {
    return h.response({ error: 'Admin access required' }).code(403);
  }
  return h.continue;
};

server.route({
  method: 'DELETE',
  path: '/admin/users/{id}',
  options: {
    auth: 'jwt',
    pre: [
      { method: isAdmin, assign: 'adminCheck' }
    ]
  },
  handler: (request, h) => {
    return deleteUser(request.params.id);
  }
});

Hapi's plugin system can be used to centralize authorization logic:

const authorizationPlugin = {
  name: 'authorization',
  version: '1.0.0',
  register: (server, options) => {
    server.decorate('request', 'checkPermission', (permission) => {
      const userScopes = request.auth.credentials.scope || [];
      return userScopes.includes(permission);
    });
  }
};

server.register(authorizationPlugin);

server.route({
  method: 'POST',
  path: '/admin/reports',
  options: {
    auth: 'jwt',
    pre: [
      { method: (request, h) => {
        if (!request.checkPermission('reports.generate')) {
          return h.response({ error: 'Permission denied' }).code(403);
        }
        return h.continue;
      }}
    ]
  },
  handler: (request, h) => {
    return generateReport();
  }
});

For Hapi applications serving AI/ML endpoints, implement additional authorization layers specific to model access and data sensitivity:

const validateAIModelAccess = (request, h) => {
  const modelRequested = request.params.model;
  const userModels = request.auth.credentials.allowedModels || [];
  
  if (!userModels.includes(modelRequested)) {
    return h.response({ error: 'Model access denied' }).code(403);
  }
  return h.continue;
};

server.route({
  method: 'POST',
  path: '/ai/{model}/generate',
  options: {
    auth: 'jwt',
    pre: [
      { method: validateAIModelAccess, assign: 'modelAccess' }
    ]
  },
  handler: (request, h) => {
    return generateAIOutput(request.payload, request.params.model);
  }
});

Frequently Asked Questions

How does middleBrick detect Broken Access Control in Hapi applications?
middleBrick uses black-box scanning to test authenticated endpoints with different user contexts, attempting parameter manipulation and scope escalation. It specifically tests Hapi's authentication flow by modifying JWT claims and testing plugin boundaries to identify authorization bypasses that static analysis might miss.
What's the difference between authentication and authorization in Hapi?
Authentication verifies who a user is (handled by Hapi's auth strategies like JWT), while authorization determines what they can access. Hapi's auth option only handles authentication - you must implement authorization separately using pre handlers, scope validation, or custom decorators to check permissions before serving protected resources.