HIGH nosql injectionfeathersjsdynamodb

Nosql Injection in Feathersjs with Dynamodb

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

FeathersJS is a framework for building JavaScript APIs with a service-oriented architecture. When using the feathers-dynamodb adapter, queries are often constructed from user-supplied parameters such as query filters, sorting fields, and pagination tokens. If these inputs are merged into the DynamoDB request without validation or escaping, an attacker can inject unexpected filter expressions or key condition expressions that change the logical intent of the query.

DynamoDB itself does not use a query language like SQL; instead, queries are expressed as structured request objects with KeyConditionExpression, FilterExpression, and expression attribute values. In FeathersJS, a typical service method may forward incoming query parameters directly to the adapter, which builds a DynamoDB request. For example, a query like /messages?userId=123 might produce a Query input where KeyConditionExpression: "userId = :uid" and :uid = 123. If an attacker supplies userId[$ne]= or embeds logical operators in a field that is naively concatenated, they can manipulate which items are returned or cause a full table scan.

Consider a Feathers service configured with the DynamoDB adapter:

const feathers = require('@feathersjs/feathers');
const rest = require('@feathersjs/rest');
const bodyParser = require('body-parser');
const ddbAdapter = require('feathers-dynamodb');

const app = feathers();
app.use('/messages', rest().setup(app));
app.configure(rest());
app.configure(bodyParser.json());
app.configure(ddbAdapter({
  tableName: 'Messages',
  partitionKey: 'userId',
  sortKey: 'timestamp'
}));

If the adapter constructs the request by directly assigning user query properties into the DynamoDB request object, an attacker could send:

GET /messages?userId[$in]=123,456&$where=attribute_exists(sortKey)

The $in and $where operators may be interpreted by the adapter as expression fragments rather than rejected as invalid, leading to a NoSQL Injection that alters the query behavior. In the worst case, this can bypass intended access controls (e.g., reading other users' messages) or force evaluation of expressions that consume excessive read capacity. While FeathersJS does not introduce the vulnerability by itself, its convenience in passing query objects to the adapter can unintentionally expose DynamoDB to injection when input validation is omitted.

Another vector involves pagination and sorting parameters. If a client sends sort=userId&order[$regex]=^admin, and the Feathers service uses these values to construct a KeyConditionExpression or FilterExpression, an attacker can inject logical operators that change the partition key condition or inject false positives in filter logic. This maps to the broader OWASP API Top 10 Injection category and can map to compliance frameworks such as SOC 2 and GDPR when personal data is improperly exposed.

Dynamodb-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on strict schema validation, whitelisting of allowed fields, and avoiding direct concatenation of user input into DynamoDB expression strings. Below are concrete, syntactically correct examples for a Feathers service using the DynamoDB adapter.

1. Validate and sanitize query parameters

Use a validation library to ensure that only expected fields are passed to the adapter. For example, using ajv:

const Ajv = require('ajv');
const ajv = new Ajv();

const querySchema = {
  type: 'object',
  properties: {
    userId: { type: 'string', pattern: '^[A-Za-z0-9_-]+$' },
    limit: { type: 'integer', minimum: 1, maximum: 100 },
    sort: { enum: ['timestamp', 'createdAt'] },
    order: { enum: ['asc', 'desc'] }
  },
  additionalProperties: false
};

const validateQuery = ajv.compile(querySchema);

In your Feathers service hook, validate before passing to the adapter:

app.service('messages').hooks({
  before: {
    async find(context) {
      const valid = validateQuery(context.params.query);
      if (!valid) {
        throw new Error('Invalid query parameters');
      }
      // Map validated fields to DynamoDB expression
      const { userId, limit, sort, order } = context.params.query;
      context.params.query.dynamo = {
        KeyConditionExpression: 'userId = :uid',
        ExpressionAttributeValues: { ':uid': userId },
        Limit: limit,
        ScanIndexForward: order === 'asc' ? true : false
      };
      return context;
    }
  }
});

2. Use expression attribute names and values only from whitelists

Never allow user input to directly name attribute names in KeyConditionExpression or FilterExpression. Instead, map allowed fields to fixed expression strings:

const allowedSortKeys = { timestamp: 'timestamp', createdAt: 'createdAt' };
const allowedOrders = { asc: true, desc: false };

function buildDynamoParams(query) {
  const params = {
    KeyConditionExpression: 'userId = :uid',
    ExpressionAttributeValues: { ':uid': query.userId }
  };

  if (allowedSortKeys[query.sort]) {
    params.ScanIndexForward = (query.order || 'asc') === 'asc';
  }
  if (query.limit) {
    params.Limit = Math.min(query.limit, 100);
  }
  return params;
}

Then in the hook:

context.params.query.dynamo = buildDynamoParams(context.params.query);

3. Avoid dynamic concatenation of expressions

Do not construct KeyConditionExpression or FilterExpression via string concatenation with user input. Instead, use the structured request objects that the adapter expects and keep expressions static where possible.

4. Use the middleware to enforce least privilege

Ensure that the IAM role associated with the DynamoDB adapter only permits the actions and resources required for the service (e.g., dynamodb:Query on the specific table and index). This limits the impact of any injection that may bypass application-layer controls.

5. Example of a secure Feathers service setup

const feathers = require('@feathersjs/feathers');
const rest = require('@feathersjs/rest');
const bodyParser = require('body-parser');
const ddbAdapter = require('feathers-dynamodb');

const app = feathers();
app.use('/messages', rest().setup(app));
app.configure(rest());
app.configure(bodyParser.json());

app.configure(ddbAdapter({
  tableName: 'Messages',
  partitionKey: 'userId',
  sortKey: 'timestamp',
  // Provide a function that returns a safe DynamoDB request
  buildQuery: function (service, method, params) {
    const query = params.query || {};
    return {
      KeyConditionExpression: 'userId = :uid',
      ExpressionAttributeValues: { ':uid': query.userId },
      Limit: query.limit ? Math.min(query.limit, 100) : undefined
    };
  }
}));

By combining input validation, whitelisting, and avoiding dynamic expression construction, the risk of NoSQL Injection in a FeathersJS + DynamoDB stack is substantially reduced while preserving the performance and scalability benefits of the managed database.

Frequently Asked Questions

Can NoSQL Injection in FeathersJS with DynamoDB bypass authentication?
Yes, if user input is directly used to construct DynamoDB expressions, attackers can manipulate query logic to access data they should not see, effectively bypassing intended access controls.
Does using middleBrick reduce the risk of NoSQL Injection in this stack?
middleBrick scans unauthenticated attack surfaces and can detect signs of NoSQL Injection patterns in API behavior and spec-to-runtime mismatches, providing findings and remediation guidance; it does not automatically fix the code.