HIGH broken access controlexpressdynamodb

Broken Access Control in Express with Dynamodb

Broken Access Control in Express with Dynamodb — how this specific combination creates or exposes the vulnerability

Broken Access Control occurs when an API fails to enforce proper authorization checks, allowing one user to act on another user's resources. In an Express application using DynamoDB as the data store, this typically arises from missing or incomplete authorization logic combined with DynamoDB's key-based data model.

Express does not provide built-in authorization; developers must explicitly implement access controls. A common pattern is to store user-owned resources in DynamoDB with a partition key like userId. If an endpoint accepts an id from the client and directly queries DynamoDB without verifying that the requested id belongs to the authenticated user, an attacker can change the identifier to access other users' data. This is a classic BOLA/IDOR (Broken Level Authorization / Insecure Direct Object Reference) pattern.

With DynamoDB, the risk is amplified when developers use a Global Secondary Index (GSI) or secondary key design that inadvertently exposes data. For example, using a composite key like PK = USER#userId and SK = ORDER#orderId is safe only if every query validates both the partition key and the ownership relationship. If authorization is skipped or performed inconsistently (for example, only checking user roles and not user ownership), an attacker can enumerate or manipulate object references by iterating through known IDs.

Additionally, DynamoDB's permission model (IAM policies) is often misconfigured in development or copied from examples, granting broader read/write access than necessary. While IAM policies are essential for defense in depth, they should not replace application-level authorization. Relying solely on IAM can lead to over-privileged roles, and if an attacker compromises an application server or steals credentials, they can access data across users. In an Express API, this happens when the SDK client is initialized with a role that has dynamodb:GetItem on a table without scoping to a specific user prefix.

Another subtle issue involves unauthenticated LLM endpoints that expose DynamoDB-backed data. If an Express route serving user data is accidentally exposed as an unauthenticated endpoint or misconfigured in an MCP Server integration, attackers can bypass authentication entirely. This aligns with the LLM/AI Security checks in middleBrick, which detect unauthenticated LLM endpoints and system prompt leakage, highlighting the importance of tight access controls even in AI-assisted integrations.

In summary, the combination of Express routing, DynamoDB key patterns, and missing authorization checks creates a scenario where attackers can traverse object identifiers and access or modify data belonging to other users. This maps to OWASP API Top 10 #1 Broken Object Level Authorization and can lead to significant data exposure if not addressed with explicit, user-bound checks.

Dynamodb-Specific Remediation in Express — concrete code fixes

To remediate Broken Access Control when using Express and DynamoDB, enforce user-bound authorization on every request and design DynamoDB key schemas to support efficient ownership checks. Below are concrete, working examples that demonstrate secure patterns.

1. Enforce ownership via partition key

Always include the authenticated user ID in the DynamoDB key and require it for every query. Never allow the client to supply the full key.

const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();

app.get('/api/orders/:orderId', async (req, res) => {
  const { userId } = req.user; // from authentication middleware
  const { orderId } = req.params;

  const params = {
    TableName: 'AppTable',
    Key: {
      PK: `USER#${userId}`,   // ownership enforced
      SK: `ORDER#${orderId}`
    }
  };

  try {
    const { Item } = await dynamo.get(params).promise();
    if (!Item) {
      return res.status(404).json({ error: 'Not found' });
    }
    res.json(Item);
  } catch (err) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

2. Query with explicit user filter

When using GSIs, ensure the query includes the user context and validate the results before returning data.

app.get('/api/users/:userId/profile', async (req, res) => {
  const { userId } = req.user;
  const { targetUserId } = req.params;

  if (userId !== targetUserId) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  const params = {
    TableName: 'AppTable',
    IndexName: 'UserEmailIndex',
    KeyConditionExpression: 'userId = :uid',
    ExpressionAttributeValues: {
      ':uid': targetUserId
    }
  };

  try {
    const { Items } = await dynamo.query(params).promise();
    if (!Items || Items.length === 0) {
      return res.status(404).json({ error: 'Not found' });
    }
    res.json(Items[0]);
  } catch (err) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

3. Avoid broad IAM roles; scope permissions

While this is infrastructure advice, it complements application code. Use IAM roles scoped to specific prefixes and combine with application-level checks. For example, an IAM policy can restrict dynamodb:Query to keys matching USER#${cognito-identity.amazonaws.com:sub}, but the application must still validate ownership.

4. Validate and sanitize input

Prevent injection and malformed key attacks by validating IDs before using them in DynamoDB keys.

function isValidId(value) {
  return typeof value === 'string' && /^[A-Za-z0-9_-]{1,36}$/.test(value);
}

app.post('/api/data', async (req, res) => {
  const { userId } = req.user;
  const { recordId, content } = req.body;

  if (!isValidId(recordId)) {
    return res.status(400).json({ error: 'Invalid record ID' });
  }

  const params = {
    TableName: 'AppTable',
    Item: {
      PK: `USER#${userId}`,
      SK: `DATA#${recordId}`,
      content
    }
  };

  try {
    await dynamo.put(params).promise();
    res.status(201).json({ success: true });
  } catch (err) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

By combining authenticated routing, explicit ownership checks, scoped IAM, and input validation, Express applications using DynamoDB can effectively mitigate Broken Access Control. These practices align with the remediation guidance provided by middleBrick, which scans for BOLA/IDOR and provides prioritized findings with severity and remediation guidance. For ongoing assurance, consider the Pro plan with continuous monitoring and CI/CD integration via the GitHub Action to fail builds if risk scores exceed your threshold.

Frequently Asked Questions

Can IAM policies alone prevent Broken Access Control in Express with DynamoDB?
No. IAM policies provide defense in depth but should not replace application-level authorization. Always enforce user-bound checks in Express routes and include the authenticated user ID in DynamoDB key designs to prevent horizontal privilege escalation.
How does middleBrick help detect Broken Access Control in Express and DynamoDB setups?
middleBrick runs 12 security checks in parallel, including BOLA/IDOR and Property Authorization, scanning the unauthenticated attack surface. It maps findings to frameworks like OWASP API Top 10 and provides prioritized findings with severity and remediation guidance. Use the CLI (middlebrick scan ) or GitHub Action to integrate checks into your workflow.