HIGH http request smugglingfeathersjscockroachdb

Http Request Smuggling in Feathersjs with Cockroachdb

Http Request Smuggling in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability

Http Request Smuggling can occur in a Feathersjs application backed by Cockroachdb when the service sits behind a reverse proxy or load balancer that processes requests differently than Feathersjs itself. Request smuggling exploits mismatches in how bodies are parsed and in how headers like Content-Length and Transfer-Encoding are handled between the proxy and the Feathersjs server. If a Feathersjs app uses body parsers or custom middleware that do not strictly validate message boundaries, an attacker can craft requests where one request’s body is smuggled into another, bypassing intended access controls or reaching admin-only endpoints.

In this stack, Cockroachdb does not directly introduce the vulnerability; it is the interaction between the transport layer (proxy/load balancer) and the Feathersjs app that matters. However, because Feathersjs services often use data services that directly read and write to Cockroachdb, a successful smuggling attack can lead to unauthorized data access or mutation in Cockroachdb. For example, if a request is smuggled into an administrative Feathersjs service route, the attacker may execute arbitrary service methods that perform SQL-like operations against Cockroachdb, potentially reading or modifying sensitive records.

An example scenario: a Feathersjs REST API behind a proxy that accepts both Transfer-Encoding: chunked and Content-Length receives two requests back-to-back. The proxy interprets the first request as ending earlier than Feathersjs does, causing the second request’s headers or body to be attached to the first. If the second request targets a Feathersjs hook or service method that performs a this.app.service('tickets').patch(userId, data) against Cockroachdb, the operation may execute with the wrong user context, leading to IDOR or privilege escalation against the database layer.

Because Feathersjs is framework-agnostic about transports, the risk depends on which transports are enabled (e.g., REST, Socket.io) and how the app is deployed. Using only the built-in REST transport without additional middleware can reduce surface area, but if custom middleware or hooks manipulate streams or bodies without strict validation, smuggling remains possible. Therefore, it is essential to align how Feathersjs parses bodies with how the upstream proxy handles them, and to ensure that each request is processed in isolation before any Cockroachdb transaction is committed.

Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on ensuring strict request boundary handling and validating inputs before any Cockroachdb interaction. In Feathersjs, configure body parsers carefully and avoid accepting ambiguous transfer encodings when behind a proxy. Use explicit content-length validation and disable chunked transfer encodings at the proxy or application layer if not required. Ensure that Feathersjs services validate ownership and permissions on every call, so that even if smuggling is attempted, the backend will reject unauthorized operations against Cockroachdb.

Below are concrete Feathersjs service and hook examples that demonstrate safe patterns when working with Cockroachdb. These examples assume you have configured your Feathersjs app to use a REST transport and a Cockroachdb adapter (such as feathers-sequelize with a Cockroachdb connection).

1. Service definition with strict query and payload validation

// src/services/tickets/tickets.class.js
const { Service } = require('feathers-sequelize');
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(process.env.COCKRACKDB_URI, {
  dialect: 'postgres',
  protocol: 'postgres',
  port: 26257,
  dialectOptions: {
    ssl: {
      require: true,
      rejectUnauthorized: false
    }
  }
});

class TicketService extends Service {
  async create(data, params) {
    // Explicitly allow only expected fields to prevent smuggling via unexpected payload keys
    const allowedFields = ['title', 'description', 'userId'];
    const filteredData = {};
    for (const key of allowedFields) {
      if (Object.prototype.hasOwnProperty.call(data, key)) {
        filteredData[key] = data[key];
      }
    }

    // Ensure the authenticated user matches the provided userId to prevent IDOR
    if (params.user && params.user.id) {
      filteredData.userId = params.user.id;
    }

    // Proceed with Sequelize-backed Cockroachdb insert
    return super.create(filteredData, params);
  }

  async patch(id, data, params) {
    // Strictly limit patchable fields
    const allowedPatchFields = ['title', 'description'];
    const filteredData = {};
    for (const key of allowedPatchFields) {
      if (Object.prototype.hasOwnProperty.call(data, key)) {
        filteredData[key] = data[key];
      }
    }

    // Ensure the ticket belongs to the authenticated user
    const ticket = await this.get(id, params);
    if (ticket.userId !== params.user.id) {
      throw new Error('Forbidden: you can only modify your own tickets');
    }

    return super.patch(id, filteredData, params);
  }
}

module.exports = function () {
  const app = this;
  app.use('/tickets', new TicketService({
    Model: sequelize.define('ticket', {
      title: Sequelize.STRING,
      description: Sequelize.TEXT,
      userId: Sequelize.INTEGER
    }, {
      hooks: {
        beforeBulkCreate: (instances, options) => {
          // Reject any unexpected fields that could be smuggled
          instances.forEach(instance => {
            const keys = Object.keys(instance.toJSON ? instance.toJSON() : instance);
            const allowed = new Set(['title', 'description', 'userId']);
            for (const key of keys) {
              if (!allowed.has(key)) {
                throw new Error(`Unexpected field ${key} in bulk create`);
              }
            }
          });
        }
      }
    }),
    paginate: { default: 10, max: 50 }
  }));

  const ticketService = app.service('/tickets');
  ticketService.hooks({
    before: {
      all: [async context => {
        // Reject requests with both Content-Length and Transfer-Encoding to mitigate smuggling
        const req = context.params && context.params.http && context.params.http.req;
        if (req) {
          const headers = req.headers || {};
          if (headers['transfer-encoding'] && headers['content-length']) {
            throw new Error('Invalid request: conflicting Transfer-Encoding and Content-Length');
          }
        }
        return context;
      }]
    }
  });
};

2. Explicitly reject ambiguous encodings at the Feathersjs hook layer

// src/hooks/validate-headers.js
module.exports = function () {
  return async context => {
    const req = context.params && context.params.raw ? context.params.raw : (context.params && context.params.http ? context.params.http.req : null);
    if (!req) {
      return context;
    }
    const headers = req.headers || {};
    const transfer = headers['transfer-encoding'];
    const contentLength = headers['content-length'];

    // Reject if both are present (common smuggling pattern)
    if (transfer && contentLength) {
      const error = new Error('Request smuggling risk: conflicting Transfer-Encoding and Content-Length headers');
      error.code = 400;
      throw error;
    }

    // Optionally reject chunked transfer-encoding if not expected
    if (transfer && transfer.toLowerCase() !== 'chunked') {
      const error = new Error('Unsupported Transfer-Encoding');
      error.code = 400;
      throw error;
    }

    return context;
  };
};

3. Ensure proper isolation between requests in service methods

// src/services/comments/comments.class.js
const { Service } = require('feathers-sequelize');

class CommentService extends Service {
  async get(id, params) {
    const comment = await super.get(id, params);
    // Verify ownership before returning sensitive data to prevent data leakage via smuggling
    if (params.user && params.user.id && comment.userId !== params.user.id) {
      throw new Error('Forbidden');
    }
    return comment;
  }
}

module.exports = function () {
  const app = this;
  app.use('/comments', new CommentService({
    Model: sequelize.define('comment', {
      text: Sequelize.TEXT,
      userId: Sequelize.INTEGER
    })
  }));

  const commentService = app.service('/comments');
  commentService.hooks({
    before: {
      all: [async context => {
        // Enforce strict header parsing to avoid accepting malformed requests
        const req = context.params && context.params.http ? context.params.http.req : null;
        if (req) {
          const headers = req.headers || {};
          if (headers['transfer-encoding'] && headers['content-length']) {
            throw new Error('Invalid request: smuggling indicators detected');
          }
        }
        return context;
      }]
    }
  });
};

These patterns reduce the risk of Http Request Smuggling by ensuring Feathersjs services validate and isolate each request before performing Cockroachdb operations. Combine these coding practices with proxy-level hardening (consistent message parsing, disabling chunked encoding where unnecessary, and strict header enforcement) to protect the backend data layer.

Frequently Asked Questions

Can Http Request Smuggling affect authenticated endpoints in Feathersjs with Cockroachdb?
Yes. If smuggling bypasses middleware, an attacker can reach authenticated service methods that perform Cockroachdb operations, potentially escalating privileges or accessing other users’ data.
Does middleBrick detect Http Request Smuggling in Feathersjs APIs backed by Cockroachdb?
middleBrick runs black-box checks for request boundary inconsistencies and flags anomalies related to smuggling. Its findings include remediation guidance to align proxy and Feathersjs behavior, helping protect Cockroachdb operations.