HIGH excessive data exposurefeathersjscockroachdb

Excessive Data Exposure in Feathersjs with Cockroachdb

Excessive Data Exposure in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability

Excessive Data Exposure occurs when an API returns more information than necessary for a given operation, such as internal identifiers, sensitive fields, or related records that should remain unseen. In a Feathersjs application backed by Cockroachdb, this risk arises from a mismatch between what the service exposes and what the database stores, amplified by common patterns in service configuration.

Feathersjs services are often configured with a ModelService that directly maps to a database table. If the service does not explicitly limit returned fields, Feathersjs may return all columns from a Cockroachdb table, including fields that should be restricted, such as password hashes, internal status flags, or tenant identifiers. Because Cockroachdb preserves column-level definitions and types, any column present in the table schema can be returned unless explicitly filtered.

Another vector is related endpoints that expose relationships or joined data. For example, a users service might include embedded roles or permissions when queried, revealing authorization details that assist in privilege escalation. An attacker probing a Feathersjs endpoint with an unauthenticated or low-privilege context could learn usernames, email addresses, or tenant IDs that aid in reconnaissance or horizontal privilege abuse.

Query parameters also contribute to the risk. If a Feathersjs service allows $select or similar query hints that are passed through to Cockroachdb without validation, an attacker might widen the returned field set beyond intended limits. Even without explicit parameter tampering, default Feathersjs behavior may return full documents, including createdAt, updatedAt, and internal metadata, which can be sensitive in certain contexts.

Real-world examples align with common vulnerability patterns cataloged in the OWASP API Security Top 10, particularly A05:2023 — Security Misconfiguration, where overly permissive responses are treated as a configuration oversight rather than a design flaw. A concrete scenario involves a /users endpoint returning hashed passwords and role flags due to missing projection, enabling an attacker to infer administrative accounts or service accounts directly from unauthenticated queries.

Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on explicit field filtering at the Feathersjs service layer and careful query construction to avoid exposing unnecessary columns from Cockroachdb. Below are concrete, working examples that demonstrate secure configurations.

1. Define a custom before hook to enforce $select

Use Feathersjs hooks to restrict which fields are returned. This ensures only intended data is exposed, regardless of how the query is formed.

const { iff, isProvider } = require('feathers-hooks-common');

app.use('/accounts', {
  before: {
    all: [
      iff(isProvider('external'), restrictPublicFields)
    ]
  }
});

function restrictPublicFields(context) {
  const allowedPublicFields = ['id', 'username', 'email', 'createdAt'];
  if (context.params.query.$select) {
    // merge user request with safe fields
    const requested = context.params.query.$select.split(',');
    context.params.query.$select = [...new Set([...allowedPublicFields, ...requested])].join(',');
  } else {
    context.params.query.$select = allowedPublicFields.join(',');
  }
  return context;
}

2. Use a repository-style service with explicit column selection

When using a Cockroachdb client directly, define a custom service method that specifies columns via SQL projection rather than relying on default ORM/query behavior.

const { Sequelize } = require('sequelize');
const sequelize = new Sequelize('postgres://user:pass@host:26257/dbname?sslmode=require');

const User = sequelize.define('users', {
  id: { type: Sequelize.INTEGER, primaryKey: true },
  username: Sequelize.STRING,
  email: Sequelize.STRING,
  password_hash: Sequelize.STRING,
  is_admin: Sequelize.BOOLEAN,
  tenant_id: Sequelize.INTEGER
}, { timestamps: true });

app.use('/users', {
  async find(params) {
    const { query = {} } = params;
    const safeQuery = {
      where: query.$where || {},
      attributes: ['id', 'username', 'email', 'createdAt'],
      limit: query.$limit ? parseInt(query.$limit) : undefined,
      offset: query.$skip ? parseInt(query.$skip) : undefined,
      order: query.$sort ? Object.entries(query.$sort) : [['createdAt', 'DESC']]
    };
    const { count, rows } = await User.findAndCountAll(safeQuery);
    return {
      total: count,
      limit: safeQuery.limit,
      skip: safeQuery.offset,
      data: rows
    };
  }
});

3. Validate and sanitize $select and query parameters

Prevent parameter injection by validating allowed fields before passing them to Cockroachdb. This avoids accidental exposure through crafted queries.

const allowedFields = new Set(['id', 'username', 'email', 'role', 'status', 'createdAt']);

app.hooks.before.push({
  name: 'validate-query-fields',
  async hook(context) {
    const query = context.params.query || {};
    if (query.$select) {
      const requested = query.$select.split(',').map(f => f.trim());
      const filtered = requested.filter(f => allowedFields.has(f));
      if (filtered.length !== requested.length) {
        throw new Error('Invalid field requested in $select');
      }
      query.$select = filtered.join(',');
    }
    context.params.query = query;
    return context;
  }
});

4. Avoid exposing internal metadata unless explicitly required

Ensure that service configurations do not return internal fields by default. In Feathersjs, you can disable timestamps or rename internal columns to avoid accidental leakage.

app.use('/secrets', {
  async get(id, params) {
    const record = await db('secrets').where({ id }).first('id', 'value');
    if (!record) throw new errors.NotFound('Not found');
    return record;
  }
});

Related CWEs: propertyAuthorization

CWE IDNameSeverity
CWE-915Mass Assignment HIGH

Frequently Asked Questions

How can I verify that my Feathersjs service no longer exposes sensitive fields to unauthenticated users?
Run an unauthenticated request against the endpoint and inspect the returned JSON. Confirm that only intended fields such as id, username, and email are present, and that fields like password_hash, is_admin, or tenant_id are omitted. You can also use middleBrick to scan the endpoint and validate that no sensitive fields appear in the output.
Does using $select in Feathersjs automatically protect against Excessive Data Exposure?
No. $select must be validated and constrained to a whitelist of safe fields. If passed directly to Cockroachdb without validation, an attacker can manipulate the parameter to request additional columns. Always sanitize and limit $select to known-safe fields.