HIGH type confusionfeathersjsdynamodb

Type Confusion in Feathersjs with Dynamodb

Type Confusion in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability

Type confusion in a Feathersjs service that uses DynamoDB as a persistence layer occurs when application code or service hooks treat a value as one JavaScript type while DynamoDB interprets it as another. Because Feathersjs is framework-agnostic about the database and relies on adapters, developers may pass JavaScript objects directly into DynamoDB document operations without strict serialization. DynamoDB stores data as native types such as strings, numbers, booleans, binary, null, lists, and maps. When a Feathersjs service expects a specific shape and dynamically builds update expressions or condition expressions, mismatched types between the client-supplied value and the expected DynamoDB attribute type can lead to privilege escalation or data exposure.

For example, a numeric attribute such as accountId expected by a BOLA check may be supplied as a string by the client. If the service constructs a KeyConditionExpression or Filter using loose equality or dynamic concatenation, DynamoDB may coerce types differently than Feathersjs middleware, bypassing intended authorization. Additionally, if a service uses generic payloads for create and update operations, an attacker could supply a list where a map is expected, causing the update expression to behave unexpectedly and potentially overwrite authorization attributes. The combination of Feathersjs hooks that modify params without strict validation and DynamoDB’s typed attribute system means that unchecked input can change the evaluation of condition expressions, leading to IDOR or privilege escalation. This is especially risky when condition expressions like attribute_exists(partitionKey) AND userSub = :userSub rely on a string :userSub while the application logic assumes a numeric identifier, creating a subtle type mismatch that bypasses authorization.

OpenAPI/Swagger definitions that use loose type definitions (e.g., type: number without minimum or maximum) combined with runtime objects that contain lists or nested maps increase the surface for type confusion. If a developer uses generic update methods that accept a partial object and translate it into a DynamoDB UpdateExpression without strict schema enforcement, the expression may target unintended attributes or apply incorrect operators. Because DynamoDB does not throw on type mismatches in many expression clauses, the operation may succeed while violating authorization assumptions. This misalignment between Feathersjs’s flexible service layer and DynamoDB’s strict typed model is where the vulnerability manifests, often revealed through unauthenticated scans that inspect endpoint behavior and generated expressions without requiring credentials.

Dynamodb-Specific Remediation in Feathersjs — concrete code fixes

Remediation centers on strict schema validation, explicit type casting, and defensive construction of DynamoDB expressions in Feathersjs services. Always validate incoming payloads against a defined JSON Schema and enforce exact types before they reach DynamoDB document operations. Use the AWS SDK’s built-in type marshalling and unmarshalling consistently, and avoid concatenating raw values into expression strings.

Example: a Feathers service using the @feathersjs/adapter-commons and AWS SDK DocumentClient should validate and marshal explicitly:

const { DynamoDB } = require('aws-sdk');
const { iff, isProvider } = require('feathers-hooks-common');
const { v4: uuidv4 } = require('uuid');

const dynamodb = new DynamoDB.DocumentClient();
const TABLE = process.env.TABLE_NAME;

function validateAndMarshal(input) {
  if (typeof input.accountId !== 'number') {
    throw new Error('Invalid type: accountId must be a number');
  }
  // Explicitly marshal to ensure DynamoDB types are correct
  return DynamoDB.Converter.marshall(input);
}

function enforceNumericId(hook) {
  const { id } = hook.params.query;
  if (id !== undefined && typeof id !== 'number') {
    throw new Error('Invalid ID type');
  }
  return hook;
}

module.exports = function () {
  const app = this;

  app.use('/secure', {
    async create(data, params) {
      const validated = validateAndMarshal(data);
      const paramsDb = {
        TableName: TABLE,
        Item: validated
      };
      await dynamodb.put(paramsDb).promise();
      return { id: validated.accountId };
    },

    async patch(id, data, params) {
      // Ensure id is numeric to prevent type confusion in KeyConditionExpression
      if (typeof id !== 'number') throw new Error('Invalid ID type');
      const updateExpressionParts = [];
      const expressionAttributeValues = {};

      if (data.status !== undefined) {
        updateExpressionParts.push('#status = :status');
        expressionAttributeValues[':status'] = { S: String(data.status) };
      }
      if (data.score !== undefined) {
        updateExpressionParts.push('#score = :score');
        expressionAttributeValues[':score'] = { N: String(data.score) };
      }

      const updateParams = {
        TableName: TABLE,
        Key: {
          partitionKey: { N: String(id) },
          sortKey: { S: 'metadata' }
        },
        UpdateExpression: `SET ${updateExpressionParts.join(', ')}`,
        ExpressionAttributeNames: { '#status': 'status', '#score': 'score' },
        ExpressionAttributeValues: expressionAttributeValues,
        ConditionExpression: 'attribute_exists(partitionKey) AND userSub = :userSub',
        ExpressionAttributeValues: {
          ...expressionAttributeValues,
          ':userSub': { S: params.account.sub }
        }
      };

      await dynamodb.update(updateParams).promise();
      return data;
    }
  });

  // Apply hooks to enforce types
  app.service('/secure').hooks({
    before: {
      all: [iff(isProvider('external'), enforceNumericId)]
    }
  });
};

In this example, numeric IDs are enforced before constructing KeyConditionExpression or UpdateExpression, and values are explicitly marshalled to DynamoDB types to avoid coercion surprises. Condition expressions use explicit attribute existence checks with typed placeholders. Avoid dynamic string concatenation of attribute names; use ExpressionAttributeNames to prevent injection and type confusion. For updates, prefer strongly-typed input contracts and reject payloads that mix types for the same field. If using the middleBrick CLI to scan this service, it can detect inconsistencies between declared schema and runtime expression patterns, helping you catch type confusion before deployment.

Related CWEs: inputValidation

CWE IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

Can type confusion in Feathersjs with DynamoDB lead to privilege escalation?
Yes. If numeric or controlled attributes are treated as strings or lists in expressions, attackers may bypass ownership checks and access or modify other users’ data.
Does middleBrick detect type confusion in Feathersjs services using DynamoDB?
Yes. middleBrick scans unauthenticated endpoints and can identify inconsistencies between declared API types and runtime expression patterns, surfacing potential type confusion findings with remediation guidance.