HIGH insecure deserializationhapimutual tls

Insecure Deserialization in Hapi with Mutual Tls

Insecure Deserialization in Hapi with Mutual Tls — how this specific combination creates or exposes the vulnerability

Insecure deserialization in a Hapi API becomes significantly riskier when Mutual TLS (mTLS) is used without corresponding transport- and application-level safeguards. mTLS ensures that both client and server present valid certificates, which strongly authenticates endpoints and prevents on-path impersonation. However, mTLS does not protect the content of an authenticated session once a request reaches the application. If a Hapi service accepts serialized objects—such as JSON that is deserialized into complex objects, MessagePack, or custom binary formats—and does not validate integrity or enforce strict type constraints, an authenticated, mTLS-backed client can still supply malicious payloads.

Consider a Hapi endpoint that receives an mTLS-authenticated request containing a serialized user session or permissions object. If the server uses a generic deserializer (e.g., JSON.parse with additional reconstruction logic, or a library that supports prototype pollution or unsafe class resolution), an attacker who possesses a valid client certificate can craft serialized data that executes unintended code or elevates privileges during deserialization. Real-world deserialization gadget chains (e.g., involving readline or utility libraries common in Node.js) can lead to remote code execution, reflected server-side request forgery, or sensitive data exposure. Common CWEs involved include CWE-502 (Deserialization of Untrusted Data) and related injection paths. Even with mTLS, if input validation, integrity checks (such as digital signatures or hashes), and least-privilege runtime permissions are absent, the authenticated channel becomes a vector for highly trusted malicious data.

Compounded by the 12 parallel checks in middleBrick—including Input Validation, Unsafe Consumption, Property Authorization, and LLM/AI Security—the scanner can detect patterns where serialized data reaches dangerous sinks without proper validation. For example, a payload embedding $__proto__ pollution or gadget chains in nested objects may pass mTLS authentication but still be flagged as insecure deserialization. The scanner cross-references OpenAPI/Swagger specs (with full $ref resolution) against runtime behavior, identifying mismatches where endpoints accept serialized formats without schema enforcement or integrity verification. This is especially relevant when LLM interfaces or automation hooks consume the same deserialized data, expanding the attack surface. middleBrick’s Active Prompt Injection probes and Output Scanning do not test deserialization directly, but they highlight how untrusted data flows—whether serialized or generated—can propagate across system boundaries when controls are weak.

Mutual Tls-Specific Remediation in Hapi — concrete code fixes

To secure Hapi APIs with mTLS while mitigating insecure deserialization, combine strict certificate validation, tight input validation, and safe data handling. Below are concrete, working examples that demonstrate how to enforce mTLS and safely handle incoming data without relying on unsafe deserialization.

1. Enforce Mutual TLS in Hapi Server

Configure Hapi with TLS options that require client certificates and validate them against a trusted CA. This ensures only authenticated clients can reach your endpoints.

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

const init = async () => {
  const server = Hapi.server({
    port: 443,
    tls: {
      cert: fs.readFileSync('/path/to/server-cert.pem'),
      key: fs.readFileSync('/path/to/server-key.pem'),
      ca: [fs.readFileSync('/path/to/ca.pem')],
      requestCert: true,
      rejectUnauthorized: true
    }
  });

  server.route({
    method: 'POST',
    path: '/profile',
    handler: (request, h) => {
      // request.client.cert contains the verified client certificate
      const clientFingerprint = request.client.cert.fingerprint;
      // Apply business logic with verified identity, avoid deserializing raw input
      const safePayload = {
        username: request.payload.username,
        settings: request.payload.settings
      };
      return { status: 'ok', client: clientFingerprint };
    },
    options: {
      validate: {
        payload: {
          username: Joi.string().alphanum().min(3).max(30).required(),
          settings: Joi.object({
            theme: Joi.string().valid('light', 'dark'),
            notifications: Joi.boolean()
          }).unknown(false) // reject unexpected keys
        }
      }
    }
  });

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

process.on('unhandledRejection', (err) => {
  console.error(err);
  process.exit(1);
});

2. Avoid Unsafe Deserialization; Use Strict Parsing and Validation

Do not reconstruct objects from serialized formats that allow prototype pollution or dynamic class resolution. Instead, parse strictly and validate against a schema.

const safeParse = (input) => {
  // Example: accept only known fields, no nested constructors or revivers
  try {
    const parsed = JSON.parse(input);
    // Enforce shape explicitly; do not rely on parsed object structure
    if (typeof parsed.username !== 'string' || typeof parsed.settings !== 'object' || parsed.settings === null) {
      throw new Error('Invalid payload shape');
    }
    return parsed;
  } catch (err) {
    throw new Error('Invalid JSON');
  }
};

// In route handler:
handler: (request, h) => {
  const raw = request.payload.rawData; // Buffer or string from safe source
  const data = safeParse(raw);
  // Proceed with data that has been whitelisted
  return { username: data.username };
}

3. Combine mTLS with Signed Tokens for Sensitive Operations

For operations that require delegated authorization, use signed JWTs with strict validation rather than serialized objects. The client certificate identifies the client; the token carries scoped claims.

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

// Register JWT validation strategy
await server.register(jwt);

server.auth.strategy('jwt', 'jwt', {
  keys: [process.env.JWT_PUBLIC_KEY],
  verify: {
    aud: false,
    iss: false,
    sub: false
  },
  validate: (artifacts, decoded, request, h) => {
    return { isValid: true, credentials: { scope: decoded.scope, userId: decoded.sub } };
  }
});

// Apply authentication to route
server.route({
  method: 'POST',
  path: '/admin',
  options: {
    auth: 'jwt',
    validate: {
      payload: Joi.object({
        action: Joi.string().valid('create', 'delete').required()
      })
    }
  },
  handler: (request, h) => {
    return { result: 'authorized', user: request.auth.credentials.userId };
  }
});

These examples show how mTLS and strict validation coexist: mTLS provides peer authentication, while schema-based validation and avoidance of unsafe deserialization prevent malicious payloads from causing harm. middleBrick’s checks for Input Validation, Unsafe Consumption, and Property Authorization can surface deviations from these patterns, and its OpenAPI/Swagger analysis helps ensure that spec-defined schemas are enforced at runtime.

Frequently Asked Questions

Does mTLS prevent insecure deserialization attacks?
No. Mutual TLS authenticates the client but does not validate the content of the request. Insecure deserialization vulnerabilities depend on how the application processes serialized data; without strict validation and safe deserialization practices, authenticated clients can still supply malicious payloads.
How does middleBrick detect insecure deserialization risks in Hapi APIs?
middleBrick runs parallel checks including Input Validation, Unsafe Consumption, and Property Authorization, cross-referencing your OpenAPI/Swagger spec (with full $ref resolution) against runtime behavior. It identifies cases where serialized formats reach dangerous sinks without integrity checks or strict schema enforcement, even when mTLS is in use.