HIGH bola idorfeathersjsdynamodb

Bola Idor in Feathersjs with Dynamodb

Bola Idor in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability

BOLA (Broken Level of Authorization) / IDOR occurs when an API exposes one user’s resource through an identifier that should be isolated to another. In a Feathersjs service backed by DynamoDB, this commonly arises when the service exposes a generic find/update/remove endpoint and relies only on the client-supplied id without scoping the request to the authenticated subject’s tenant or user partition key. Because DynamoDB queries are defined by key expressions, an unsafe query can return items that share a partition key but should be restricted further by a sort key or record ownership field.

Consider a multi-tenant application where each tenant owns a set of configurations. A Feathers service might define a DynamoDB table with tenantId as the partition key and configName as the sort key. If the service implements a get method like get(id, params) and passes id directly to a DynamoDB get or query without ensuring that the resolved id belongs to the requester’s tenant, an authenticated user can enumerate or modify other tenants’ configurations simply by guessing or iterating IDs.

Feathers generates hooks that transform calls into DynamoDB expressions. If those hooks do not inject tenant context into the key condition, the service performs an unbounded or over-permissive query. For example, a find hook that builds a KeyConditionExpression from user input without enforcing tenantId = :tid allows horizontal privilege escalation across records sharing the same partition key. Real-world patterns include using Cognito identity IDs as partition keys; if the service does not validate that the requested item’s partition key matches the authenticated identity’s partition key, IDOR is trivially reachable.

Additionally, unsafe use of DynamoDB update with a client-supplied key can lead to BOLA when the update expression does not reassert ownership. An attacker who knows another user’s ID can craft a PATCH request to modify that record if the service does not re-check tenant or user ownership in the update path. Because DynamoDB returns errors only for missing keys rather than authorization failures, the service may mistakenly interpret a missing item as a successful no-op, masking the violation.

The interaction of Feathers’ flexible hooks and DynamoDB’s key-based model means developers must explicitly bind the authenticated subject to the key schema. Without this binding, the API surface implicitly trusts the client identifier, which is the root cause of BOLA/IDOR in this stack.

Dynamodb-Specific Remediation in Feathersjs — concrete code fixes

Remediation centers on enforcing ownership at the DynamoDB query level and ensuring hooks inject immutable context derived from the authenticated subject. Below are concrete patterns using the AWS SDK for JavaScript (v3) with Feathers hooks.

1. Scoped query with tenant partition key

Ensure every query includes the tenant identifier derived from the authenticated user. Do not rely on the client-provided ID alone.

const { DynamoDBClient, GetItemCommand, QueryCommand } = require("@aws-sdk/client-dynamodb");
const client = new DynamoDBClient({ region: "us-east-1" });

app.service("configs").hooks({
  before: {
    async get(id, params) {
      const { user } = params;
      if (!user || !user.tenantId) throw new Error("Unauthenticated");
      params.sequelizeWhere = {
        tenantId: { [Op.eq]: user.tenantId },
        configName: { [Op.eq]: id }
      };
      return params;
    },
    async find(params) {
      const { user } = params;
      if (!user || !user.tenantId) throw new Error("Unauthenticated");
      params.sequelizeWhere = {
        tenantId: { [Op.eq]: user.tenantId }
      };
      return params;
    }
  }
});

2. Parameterized KeyConditionExpression in a custom handler

If using a low-level DynamoDB adapter, bind the partition key explicitly and avoid string concatenation.

const { DynamoDBClient, QueryCommand } = require("@aws-sdk/client-dynamodb");
const client = new DynamoDBClient({ region: "us-east-1" });

async function getScopedConfig(tenantId, configName) {
  const command = new QueryCommand({
    TableName: process.env.CONFIG_TABLE,
    KeyConditionExpression: "tenantId = :tid AND configName = :cname",
    ExpressionAttributeValues: {
      ":tid": { S: tenantId },
      ":cname": { S: configName }
    }
  });
  const response = await client.send(command);
  return response.Items;
}

3. Update with ownership re-verification

For update/remove operations, re-assert the tenant-key in the condition expression to prevent unauthorized writes.

const { DynamoDBClient, UpdateItemCommand } = require("@aws-sdk/client-dynamodb");
const client = new DynamoDBClient({ region: "us-east-1" });

async function safeUpdateConfig(tenantId, configName, updateExpression, expressionAttributeValues) {
  const command = new UpdateItemCommand({
    TableName: process.env.CONFIG_TABLE,
    Key: {
      tenantId: { S: tenantId },
      configName: { S: configName }
    },
    UpdateExpression: updateExpression,
    ExpressionAttributeValues: expressionAttributeValues,
    ConditionExpression: "tenantId = :tid AND configName = :cname",
    ExpressionAttributeValues: {
      ...expressionAttributeValues,
      ":tid": { S: tenantId },
      ":cname": { S: configName }
    }
  });
  await client.send(command);
}

4. Feathers middleware to inject user context

Use a custom hook to attach tenant-aware parameters before the service handler runs.

const authHook = (context) => {
  const { user } = context.params;
  if (user && user.tenantId) {
    context.params.tenantId = user.tenantId;
  } else {
    throw new Error("Missing tenant context");
  }
  return context;
};

app.service("records").hooks({
  before: {
    all: [authHook],
    find: [
      (context) => {
        const { tenantId } = context.params;
        context.params.tenantId = tenantId;
        return context;
      }
    ]
  }
});

5. Avoid client-controlled key expressions

Do not construct DynamoDB expressions from raw IDs. Always map the client ID to a scoped key using server-side logic that includes the authenticated subject’s partition key.

6. Validation and error handling hygiene

Return generic not-found messages to avoid leaking existence information, and log authorization anomalies without exposing tenant identifiers in responses.

These patterns align with OWASP API Top 10 A01:2023 broken object-level authorization and reduce the likelihood of IDOR by binding every DynamoDB operation to the authenticated subject’s tenant or user context.

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 BOLA/IDOR in a Feathersjs + DynamoDB deployment?
middleBrick runs unauthenticated scans that inspect the API surface and the OpenAPI/Swagger spec (if provided) to verify that tenant or user context is enforced in key expressions and hooks. It checks whether find/get/update operations scope queries to the authenticated subject’s partition key and flags missing constraints as potential BOLA/IDOR findings.
Can remediation be fully automated by the scanner?
middleBrick detects and reports misconfigurations with severity and remediation guidance, but it does not modify code or enforce policies. Developers must apply DynamoDB scoped queries and hook-level tenant binding to resolve BOLA/IDOR.