HIGH nosql injectionfeathersjsjwt tokens

Nosql Injection in Feathersjs with Jwt Tokens

Nosql Injection in Feathersjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

FeathersJS is a framework for real-time applications that often uses MongoDB-style query objects for service operations. When user input is directly embedded into these query objects without validation or sanitization, a NoSQL injection vulnerability can occur. JWT tokens are commonly used for authentication in FeathersJS; they typically carry a user identity (e.g., sub, role) which the application may use to build queries. If the token payload is not strictly validated and is instead used to construct dynamic queries, an attacker who can influence or forge the token context (for example, via a compromised or unsigned token) may alter query logic.

Consider a FeathersJS service that retrieves user profiles and uses decoded JWT claims to filter results. A developer might write code like:

app.service('profiles').find({
  query: {
    userId: context.params.user.sub,
    $where: 'return true;' // dangerous if derived from token or request
  }
});

If an attacker can manipulate the token’s sub or add unexpected claims, and the service uses those claims directly in query conditions, they might coerce the query to return other users’ data. Moreover, if the service mixes user input (e.g., query parameters) with decoded JWT data to build MongoDB operators, injection can occur. For example, an endpoint that merges req.query with claims from the JWT to construct a filter could allow an attacker to inject operators like $ne, $gt, or even $where by supplying specially crafted parameters that combine with the token-derived context.

Another scenario involves role-based access control derived from JWTs. If a token includes a role claim and the app uses it to conditionally apply query filters, missing validation on the role claim can lead to privilege escalation via injection. An attacker who can get a valid but low-privilege token might attempt to append additional query properties or operators through other request parameters, relying on the service to concatenate token data with untrusted input unsafely.

FeathersJS itself does not introduce NoSQL injection; the risk arises when application code combines JWT-derived data with dynamic query construction using user-supplied input. MiddleBrick’s scans detect such patterns by correlating authentication findings (JWT usage) with query-building behavior and testing for parameter pollution, operator injection, and unsafe $where usage. The scanner does not assume trust in the token source and checks whether query composition logic properly validates and restricts inputs regardless of authentication context.

Jwt Tokens-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on strict input validation, avoiding direct use of token claims in query construction, and using parameterized queries or an ORM/ODM that enforces schema constraints. Do not concatenate user input or token claims into raw query objects.

1. Validate and sanitize JWT claims

Treat decoded JWT data as untrusted. Validate issuer, audience, expiration, and expected fields. Only use claims for authorization decisions after verification, not for building raw queries.

const jwt = require('@feathersjs/authentication-jwt');
const { express } = require('@feathersjs/feathers');

const app = express();
// Configure JWT with strict options
app.configure(jwt({
  secret: process.env.JWT_SECRET,
  algorithms: ['HS256'],
  issuer: 'myapp',
  audience: 'myapp-users'
}));

2. Use service hooks to sanitize inputs and enforce ownership

In a hook, ensure the query is scoped to the authenticated user and that external parameters are validated. Avoid merging raw query params with token data unsafely.

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

function restrictToOwnUser() {
  return (context) => {
    const { user } = context.params;
    if (user && user.sub) {
      // Explicitly scope the query to the authenticated user's ID
      context.params.query = {
        $and: [
          context.params.query || {},
          { userId: user.sub }
        ]
      };
    }
    return context;
  };
}

app.service('profiles').hooks({
  before: {
    all: [iff(isProvider('external'), restrictToOwnUser())],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
});

3. Avoid $where and prefer typed queries

Do not allow JavaScript code execution via $where. Use schema-based validation and typed query parameters. If using MongoDB, prefer operators like $eq, $in with strict value types, and validate each value.

app.service('users').find({
  query: {
    role: { $in: ['user', 'admin'] },
    status: 'active'
    // Do not include unsanitized $where or concatenated conditions from token claims
  }
});

4. Combine token-based authorization with explicit checks

After JWT verification, enforce authorization at the service or hook layer rather than encoding it into the query via token claims. For example, allow an admin to query multiple users, but ensure the query structure cannot be hijacked to inject new conditions.

app.service('users').hooks({
  before: {
    all: [
      (context) => {
        const { user, params } = context;
        if (user && user.isAdmin && params.query && params.query._id) {
          // Admin can query specific IDs safely if IDs are validated
          params.query._id = { $in: Array.isArray(params.query._id) ? params.query._id : [params.query._id] };
        } else if (user && user.sub) {
          context.params.query = { $and: [params.query || {}, { userId: user.sub }] };
        }
        return context;
      }
    ]
  }
});

5. Use an ODM/ODM layer and parameterized APIs

Leverage an ODM that enforces schema types and does not allow raw injection of operator-like keys from uncontrolled sources. Validate and cast all inputs before they reach the service layer.

const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
  userId: String,
  email: String,
  role: { type: String, enum: ['user', 'admin'] }
});
const UserModel = mongoose.model('User', userSchema);

// In a Feathers hook or service method
async function safeFindUsers(params) {
  const query = {};
  if (params.query && params.query.role) {
    query.role = params.query.role; // validated against enum
  }
  if (params.user && params.user.sub) {
    query.userId = params.user.sub;
  }
  return UserModel.find(query).exec();
}

Frequently Asked Questions

Can a JWT token itself contain malicious operators that lead to NoSQL injection?
JWT tokens carry claims, not executable operators. Injection occurs when these claims are improperly used to build queries. Always validate and scope claims; do not directly embed token data into raw query objects.
Does using JWTs in FeathersJS automatically protect against NoSQL injection?
No. Authentication via JWT does not sanitize or validate how claims are used in query construction. You must still validate inputs, avoid dynamic query assembly with token claims, and enforce strict access controls.