HIGH insecure direct object referencefeathersjsdynamodb

Insecure Direct Object Reference in Feathersjs with Dynamodb

Insecure Direct Object Reference in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability

Insecure Direct Object Reference (IDOR) occurs when an API exposes internal object references—such as record IDs—and allows an authenticated subject to access or modify data without proper authorization checks. In a Feathersjs service backed by DynamoDB, this commonly manifests when an endpoint like /users/:id uses the route parameter directly as a key in a get or query call against DynamoDB without verifying that the authenticated user owns or is permitted to access that item.

Consider a typical Feathersjs service definition using feathers-dynamodb. If the service relies solely on the incoming params.id to fetch a record, an attacker who can obtain or guess valid IDs can iterate through them and read other users’ data. For example, a request to /users/123 with an attacker’s token might return another user’s profile if the service does not scope the query to the authenticated user’s identity. This becomes more nuanced when partition keys and sort keys are used: a service might use a composite key such as PK = USER#123 and SK = PROFILE. If the service reads params.id and constructs a key without ensuring it matches the authenticated subject’s user ID, the request retrieves an unintended item, resulting in IDOR.

DynamoDB itself does not enforce application-level ownership; it returns the item if the key matches. Therefore, the responsibility to enforce ownership and access control lies with the Feathersjs service logic. Without explicit checks—such as comparing the authenticated user ID from the token (available in params.user when configured with authentication) with the ID embedded in the key—an attacker can traverse records by incrementing IDs or enumerating known identifiers. Real-world attack patterns include horizontal IDOR (accessing same-privilege accounts) and vertical IDOR (escalating to admin records). The OWASP API Security Top 10 lists IDOR as a prevalent risk, and in serverless contexts, misconfigured DynamoDB key schemas amplify the impact by making traversal straightforward.

Additionally, secondary effects emerge when IDOR is combined with other unchecked inputs. If the service allows filtering or searching via query parameters that map to DynamoDB scan or query conditions, an attacker might leverage injection or parameter pollution to widen the scope of accessible data. For instance, an endpoint that accepts a status filter must still scope the query to the user’s partition key; otherwise, an attacker could supply arbitrary values to enumerate records beyond their scope. This underscores the need to bind every data access operation to the requester’s identity and validate inputs against the expected schema, ensuring that only intended records are retrieved or modified.

Dynamodb-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on ensuring every DynamoDB access is scoped to the authenticated subject and validated against the application’s authorization model. In Feathersjs, this means explicitly using the authenticated user’s identity from params.user to construct keys and adding checks before performing get, create, update, or remove operations.

Below are concrete, syntactically correct examples for a Feathersjs service using feathers-dynamodb. The examples assume the service is configured with authentication that populates params.user.id (or params.user.sub) and that the DynamoDB table uses a composite key where the partition key encodes the user identifier.

Secure get by ID

Instead of directly using params.id, derive the partition key from the authenticated user and compare it with the requested ID. If they do not match, throw an error.

const { Forbidden } = require('@feathersjs/errors');

app.service('users').hooks({
  before: {
    get: [context => {
      const { id } = context.params.id; // e.g., 'USER#123|PROFILE'
      const userIdentity = context.params.user.id; // authenticated user ID
      // Ensure the requested key belongs to the authenticated user
      if (!id.startsWith('USER#' + userIdentity)) {
        throw new Forbidden('Access denied: invalid object reference');
      }
      // Optionally enforce additional constraints on sort key or attributes
      return context;
    }]
  }
});

Secure query with scoped partition key

When querying, always set the partition key to the authenticated user’s key and avoid allowing raw user input to dictate the key condition. Use DynamoDB’s query with a KeyConditionExpression that binds to the user-specific partition key.

const { Forbidden } = require('@feathersjs/errors');

app.service('user-items').hooks({
  before: {
    find: [context => {
      const userId = context.params.user.id;
      // Build the partition key from the authenticated user
      const partitionKey = 'USER#' + userId;
      // Optionally allow a sort key filter but ensure it’s scoped
      const sortKeyCondition = context.params.query.sortKeyCondition || begins_with(sortKeyAttr, sortKeyPrefix);
      // Explicitly set query parameters to avoid scanning the entire table
      context.params.query = {
        ...context.params.query,
        partitionKey,
        // Ensure KeyConditionExpression or equivalent is used per adapter
        keyCondition: { attributeName: 'pk', value: partitionKey }
      };
      return context;
    }]
  }
});

Secure create/update/remove with ownership check

For write operations, verify that any identifier provided by the client matches the authenticated user’s scope, or generate server-side identifiers to avoid reliance on client-supplied keys.

app.service('user-settings').hooks({
  before: {
    create: [context => {
      const userId = context.params.user.id;
      // If the client provides an ID, ensure it matches the user
      if (context.data.id && context.data.id !== userId) {
        throw new Forbidden('Cannot create resource for another user');
      }
      // Assign the server-side ID to enforce ownership
      context.data.id = userId;
      context.data.pk = 'USER#' + userId;
      return context;
    }],
    update: [context => {
      const userId = context.params.user.id;
      const { id } = context.params.query; // or from params.id for direct updates
      if (id && !id.startsWith('USER#' + userId)) {
        throw new Forbidden('Cannot update resource owned by another user');
      }
      return context;
    }],
    remove: [context => {
      const userId = context.params.user.id;
      const { id } = context.params.query;
      if (id && !id.startsWith('USER#' + userId)) {
        throw new Forbidden('Cannot delete resource owned by another user');
      }
      return context;
    }]
  }
});

These patterns enforce that every DynamoDB operation is bound to the authenticated subject’s identity, preventing IDOR by design. They align with remediation guidance that emphasizes scoping, input validation, and explicit ownership checks rather than relying on the client-supplied identifier alone.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

How does middleBrick detect IDOR in Feathersjs services using DynamoDB?
middleBrick runs unauthenticated scans that test whether endpoints expose internal object references without proper ownership checks. It analyzes OpenAPI specs and runtime behavior to identify cases where route parameters are used directly as DynamoDB keys without scoping to the authenticated user, flagging potential IDOR.
Can middleBrick fix IDOR findings automatically?
middleBird detects and reports IDOR with severity, descriptions, and remediation guidance. It does not automatically patch or block requests; developers must apply the suggested fixes, such as scoping DynamoDB queries to the authenticated user and validating identifiers before data access.