HIGH sandbox escapefeathersjsdynamodb

Sandbox Escape in Feathersjs with Dynamodb

Sandbox Escape in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability

FeathersJS is a framework that encourages a service-oriented architecture where each entity is a Feathers service. When using Dynamodb as the persistence layer, developers often rely on the AWS SDK to perform low-level operations. If service parameters are passed directly into DynamoDB condition expressions, scan filters, or key attribute lookups without strict validation, an attacker can supply values that change the logical structure of the request. A sandbox escape occurs when user-controlled input influences which table or index is queried, which partition key is used, or whether a filter is applied at all, allowing reads or writes outside the intended logical scope.

For example, consider a Feathers service configured with a generic DynamoDB adapter where the table name is derived from a request parameter or a header. An authenticated user could change the target table by supplying a different name, potentially accessing tables that store other tenants’ data or administrative records. Similarly, if the partition key value is taken directly from user input and used in a GetItem or Query call, an attacker can pivot to another partition key, enumerating records they should not see. Dynamic filter construction that embeds user input into expressions without whitelisting can also bypass intended filtering, effectively turning a scoped query into a broader scan.

These patterns are especially risky when the Feathers service relies on unauthenticated or loosely authenticated endpoints. Because middleBrick tests unauthenticated attack surfaces by default, misconfigured endpoints that expose DynamoDB operations can be discovered quickly. A service that does not validate input against allowed values for fields such as userId, orgId, or resourceId may inadvertently allow horizontal privilege escalation across users or organizations. The combination of FeathersJS’s flexible service mapping and DynamoDB’s low-level API increases the chance that an intended safeguard (such as a scoped query) is undermined by unchecked input.

Real-world attack patterns like IDOR and BOLA often map to these vectors. If a developer uses a DynamoDB condition expression such as attribute_exists(partitionKey) AND userId = :uid but binds :uid from untrusted data without normalization, an attacker can manipulate the binding to reference another user’s identifier. In more complex setups, supplementary parameters like indexName or select can redirect the operation to a different index or projection, leading to data exposure. middleBrick’s checks for BOLA/IDOR and Property Authorization are designed to surface these risks by correlating runtime behavior with OpenAPI intent and DynamoDB access patterns.

Remediation focuses on strict input validation and scoping at the service layer. Do not construct table names, index names, or key attribute values from raw request parameters. Use enumerated constants for table identifiers and enforce ownership checks before invoking any DynamoDB operation. For filters, prefer expression attribute values over dynamic expression fragments, and validate that the resulting scope aligns with the authenticated subject. middleBrick’s findings include concrete remediation guidance to help you tighten these controls without relying on assumptions about client behavior.

Dynamodb-Specific Remediation in Feathersjs — concrete code fixes

Apply strict validation and canonicalization before any DynamoDB interaction. Define allowed values for identifiers and map them to internal constants instead of using raw input. Below are examples that demonstrate a secure Feathers service using the AWS SDK for JavaScript v3 with DynamoDB.

1. Parameterized Query with Validation

Ensure that user input is validated against a whitelist and converted to a stable partition key before querying.

import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
import { fromIni } from "@aws-sdk/credential-providers";

const client = new DynamoDBClient({ credentials: fromIni() });

const ALLOWED_TABLES = new Set(["users", "orgs", "audit"]);

async function safeGetItem(tableHint, key) {
  const tableName = ALLOWED_TABLES.has(tableHint) ? tableHint : "users";
  const command = new GetItemCommand({
    TableName: tableName,
    Key: {
      id: { S: key.id },
    },
  });
  const response = await client.send(command);
  return response.Item;
}

2. Scoped Query with Expression Attribute Values

Use expression attribute values for all user inputs and avoid concatenating strings into expressions.

import { DynamoDBClient, QueryCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({ region: "us-east-1" });

async function queryByUserId(userId, sortKeyCondition) {
  const params = {
    TableName: "tenant_data",
    IndexName: "gsi_user",
    KeyConditionExpression: "pk = :pk AND sk <= :sk",
    ExpressionAttributeValues: {
      ":pk": { S: `USER#${userId}` },
      ":sk": { S: sortKeyCondition },
    },
  };
  const { Items } = await client.send(new QueryCommand(params));
  return Items;
}

3. Enforce Row-Level Security at the Service Layer

Before invoking DynamoDB, normalize and verify that the resource belongs to the requesting subject.

import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({});

async function updateUserRecord(userId, recordId, update) {
  if (!isValidUuid(recordId) || !belongsToUser(userId, recordId)) {
    throw new Error("Unauthorized");
  }
  const params = {
    TableName: "user_records",
    Key: {
      pk: { S: `USER#${userId}` },
      sk: { S: `RECORD#${recordId}` },
    },
    UpdateExpression: "set #data = :data",
    ExpressionAttributeNames: { "#data": "data" },
    ExpressionAttributeValues: {
      ":data": { S: JSON.stringify(update) },
    },
  };
  await client.send(new UpdateItemCommand(params));
}

4. Avoid Dynamic Table or Index Names

Do not pass table or index names from request parameters. Use constants or configuration maps instead.

const TABLE_MAP = {
  profiles: "profiles_v1",
  settings: "settings_v1",
};

function resolveTable(reference) {
  return TABLE_MAP[reference] || TABLE_MAP.profiles;
}

// Usage: const tableName = resolveTable(req.body.tableRef);

5. Validate and Normalize Input for Condition Expressions

When using ConditionExpression, ensure attribute names are static and values are bound safely.

async function conditionalPut(table, item) {
  const params = {
    TableName: table, // must be resolved via constants, not user input
    Item: item,
    ConditionExpression: "attribute_exists(#id)",
    ExpressionAttributeNames: { "#id": "id" },
  };
  await client.send(new PutItemCommand(params));
}

By combining these patterns, you reduce the attack surface that could enable a sandbox escape. middleBrick’s scans can verify that your service endpoints enforce these constraints and that no unchecked parameters reach DynamoDB operations.

Frequently Asked Questions

Can an attacker change the DynamoDB table name via a Feathers service if the table name is derived from user input?
Yes, if the table name is constructed from unvalidated request data, an attacker can redirect operations to arbitrary tables, leading to sandbox escape and potential data exposure across tenants.
How does middleBrick detect sandbox escape risks involving DynamoDB in FeathersJS services?
middleBrick runs unauthenticated scans that test boundary inputs, validates parameter usage in queries and condition expressions, and correlates findings with OWASP API Top 10 and property-based authorization checks to highlight potential bypasses.