HIGH data exposurefeathersjsjwt tokens

Data Exposure in Feathersjs with Jwt Tokens

Data Exposure in Feathersjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

FeathersJS is a framework for real-time APIs that commonly uses JWT tokens for stateless authentication. When JWT tokens are mishandled in a FeathersJS service, sensitive data can be exposed through multiple vectors. A typical Feathers service exposes endpoints that rely on an authenticated JWT in the Authorization header. If the service returns full user records—including password hashes, secret fields, or internal relations—after validating the token, an authenticated context can still leak sensitive information.

One common pattern is attaching the entire user payload returned by the JWT payload (decoded from the token) directly into the response. For example, a Feathers hook might pass req.user through to the result without filtering. Because JWTs are self-contained, the token may include claims like roles, permissions, or identifiers that should not be surfaced to the client if they are not needed. If the API response includes these fields, an attacker who obtains the token can infer sensitive role mappings or scope, leading to privilege escalation or data exposure beyond what the application intended to reveal.

Another vector involves misconfigured service hooks that merge query parameters or request bodies with the authenticated user object. Suppose a user profile endpoint allows partial updates and directly assigns incoming JSON onto the user record retrieved via the JWT subject. Without strict schema validation, an attacker can supply fields that overwrite sensitive attributes or read fields they should not see. Because FeathersJS often relies on hooks for authorization, a missing or incorrectly ordered hook can skip checks and return raw data that includes fields like internal IDs, email addresses, or tokens intended for backend use only.

Middleware and hook ordering also contribute to exposure. If authentication hooks that decode the JWT are placed after data retrieval hooks, the service may fetch records before confirming the token is valid and that the requesting user is authorized for those records. This ordering flaw can inadvertently expose records belonging to other users when combined with weak or missing ownership checks. Even with valid JWT tokens, an attacker can iterate over identifiers if the service does not enforce row-level ownership based on the JWT subject claim.

Logging and error messages further amplify data exposure. FeathersJS services that log full request or response payloads may inadvertently write JWT claims or user data to logs. If error responses include stack traces or internal field names, they can reveal whether a JWT-based user exists or expose internal data structures. Properly scoped tokens with minimal claims reduce the impact, but developers must ensure that responses do not echo back sensitive fields present in the token or the database record.

Finally, token storage and transmission practices affect exposure. If frontend clients store JWTs in insecure locations and transmit them over non-TLS channels, tokens can be intercepted, leading to unauthorized data access. FeathersJS APIs that do not enforce HTTPS or that accept tokens from insecure origins increase the risk that intercepted tokens will be used to read or modify data. Combining secure transport, conservative token claims, strict hook ordering, and output filtering is essential to prevent data exposure when using JWTs with FeathersJS.

Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on minimizing claims in the JWT, strict hook ordering, and explicit filtering of returned data. Ensure that tokens issued by your identity provider contain only necessary claims such as sub, role, and scope. In FeathersJS, configure authentication to validate the token and attach a sanitized user object to req.user, excluding sensitive fields before they reach services.

// src/hooks/authentication.js
const { AuthenticationError } = require('@feathersjs/errors');
const jwt = require('jsonwebtoken');

module.exports = function authentication(options) {
  return async context => {
    const { headers } = context.params;
    const token = headers && headers.authorization && headers.authorization.split(' ')[1];
    if (!token) {
      return context;
    }
    try {
      const payload = jwt.verify(token, process.env.JWT_SECRET);
      // Sanitize: only keep safe claims
      context.params.user = {
        id: payload.sub,
        role: payload.role,
        scope: payload.scope || 'read'
      };
      // Explicitly remove sensitive claims
      delete context.params.user.passwordHash;
      delete context.params.user.emailVerifiedAt;
      return context;
    } catch (error) {
      throw new AuthenticationError('Invalid token');
    }
  };
};

Next, enforce field-level filtering in service find and get methods to avoid returning sensitive fields even if they exist in the database record. Use a hook that removes properties based on the requesting user’s role derived from the JWT claims.

// src/hooks/secure-results.js
module.exports = function secureResults(options) {
  return async context => {
    const user = context.params.user;
    if (!user || !user.role) {
      throw new Error('Unauthenticated context');
    }
    const isAdmin = user.role === 'admin';
    // Filter response shape
    if (context.result && context.result.data) {
      context.result.data = context.result.data.map(entry => sanitizeEntry(entry, isAdmin));
    } else if (context.result && context.result.total) {
      context.result.data = context.result.data.map(entry => sanitizeEntry(entry, isAdmin));
    } else if (context.result) {
      context.result = sanitizeEntry(context.result, isAdmin);
    }
    return context;
  };
};

function sanitizeEntry(entry, isAdmin) {
  const sanitized = { ...entry };
  // Always remove these fields
  delete sanitized.passwordHash;
  delete sanitized.resetToken;
  delete sanitized.emailVerifiedAt;
  // Role-based exposure
  if (!isAdmin) {
    delete sanitized.internalNotes;
    delete sanitized.auditLog;
    // Ensure PII is limited
    if (sanitized.email) {
      sanitized.email = sanitized.email.split('@')[0] + '@[hidden]';
    }
  }
  return sanitized;
}

Configure FeathersJS services to apply these hooks in the correct order: authentication first, then authorization, then data retrieval, and finally result filtering. This ordering ensures that the user derived from the JWT is available before any data is fetched and that sensitive fields are removed before the response leaves the server.

// src/services/users/users.service.js
const { iff, isProvider } = require('feathers-hooks-common');
const authenticateJwt = require('./hooks/authentication');
const secureResults = require('./hooks/secure-results');

module.exports = function (app) {
  const options = {
    name: 'users',
    paginate: { default: 25, max: 50 }
  };

  app.use('/users', createService(options));
  const service = app.service('users');

  service.hooks({
    before: {
      all: [iff(isProvider('external'), authenticateJwt)],
      find: [],
      get: [],
      create: [],
      update: [],
      patch: [],
      remove: []
    },
    after: {
      all: [secureResults()],
      find: [],
      get: [],
      create: [],
      update: [],
      patch: [],
      remove: []
    },
    error: {
      all: [],
      find: [],
      get: [],
      create: [],
      update: [],
      patch: [],
      remove: []
    }
  });
};

Additionally, validate and limit the claims accepted from the JWT to prevent token smuggling or privilege escalation via manipulated tokens. Reject tokens with unexpected issuers or audiences, and enforce short expiration times to reduce the window for exposure. Combine these practices with HTTPS enforcement and secure storage on the client to minimize data exposure when JWTs are used with FeathersJS.

Finally, audit your service responses to ensure no sensitive fields leak. Use automated tests that simulate authenticated requests with varying role claims derived from the JWT and assert that restricted fields are absent. This verification complements the runtime hooks and helps maintain a minimal data exposure posture across your FeathersJS API surface.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

Can middleBrick scan a FeathersJS API protected by JWT tokens?
Yes. middleBrick scans the unauthenticated attack surface by default, so it can analyze endpoints even when JWT protection is in place. For deeper authenticated scans, you can provide a valid token if supported by the target configuration.
Does middleBrick fix the data exposure findings it reports for FeathersJS?
No. middleBrick detects and reports findings with severity and remediation guidance. It does not fix, patch, block, or remediate. You should apply the code-level fixes described in the remediation guidance to address data exposure.