HIGH data exposureexpressdynamodb

Data Exposure in Express with Dynamodb

Data Exposure in Express with Dynamodb

Data exposure in an Express service that uses DynamoDB typically occurs when application logic fails to enforce authorization at the field or record level before returning data to the client. Because DynamoDB is a low-level, schema-flexible store, it does not enforce attribute-level permissions; any data a query or scan returns is data the caller receives. If Express routes do not validate ownership or required scopes before passing query parameters to DynamoDB, they can inadvertently expose other users’ data, sensitive attributes, or entire items.

For example, consider an Express route that retrieves a user profile by ID using a path parameter like /api/users/:userId. If the route uses the incoming userId directly to construct a DynamoDB query without confirming the authenticated caller owns that user, any authenticated user can enumerate IDs and access other profiles. A vulnerable implementation might look like:

app.get('/api/users/:userId', async (req, res) => {
  const { userId } = req.params;
  const params = {
    TableName: 'Users',
    Key: { userId: userId }
  };
  const result = await dynamodb.get(params).promise();
  res.json(result.Item);
});

Here, the route trusts userId from the URL. An attacker can change the ID and read arbitrary user records. This becomes more impactful when items contain sensitive fields such as email, role, or internal identifiers. Because DynamoDB can return large nested documents, an attacker may also receive fields that should never be exposed to the caller, such as password hashes, tokens, or internal status flags.

Another data exposure pattern involves scans or queries that return more items than intended due to missing filtering on the server side. For instance, an endpoint that lists user activity might use a scan with a filter expression but omit an authorization check on the tenant or user identifier. Since scans read every item and filter expressions are applied after reading, sensitive records can be exposed before the filter removes them. A DynamoDB scan in Express might look like:

app.get('/api/activity', async (req, res) => {
  const params = {
    TableName: 'Activity',
    FilterExpression: 'entityId = :eid',
    ExpressionAttributeValues: { ':eid': req.query.entityId }
  };
  const result = await dynamodb.scan(params).promise();
  res.json(result.Items);
});

Without verifying that the caller is allowed to view entityId, this endpoint can leak activity records belonging to other users or tenants. Attribute-level sensitivity is also a concern: even if a query returns only the intended records, fields like ssn or creditCard may be included when they should be masked or omitted based on caller permissions.

Finally, misconfigured DynamoDB responses can expose metadata or raw attribute values that should be transformed before reaching the client. For example, DynamoDB’s native type wrappers (e.g., { S: 'value' }) may be returned directly to API consumers if serialization is not normalized. This can lead to inconsistent handling of data exposure in downstream clients and logging systems that inadvertently store sensitive information.

Dynamodb-Specific Remediation in Express

To prevent data exposure when using DynamoDB in Express, enforce authorization before constructing requests, limit returned attributes, and normalize responses. Always resolve references such as $ref in OpenAPI specs against runtime behavior to ensure the exposed surface matches documented contracts.

First, scope every DynamoDB operation to the authenticated user. Instead of trusting path parameters, derive the user identifier from the authentication context and use it as a key condition or filter. For example:

app.get('/api/users/me', async (req, res) => {
  const userId = req.user.sub; // from auth middleware
  const params = {
    TableName: 'Users',
    Key: { userId: userId },
    ProjectionExpression: 'userId,email,role' // limit exposed fields
  };
  const result = await dynamodb.get(params).promise();
  res.json(result.Item);
});

Using ProjectionExpression reduces data exposure by ensuring only necessary attributes are returned. This is especially important for sensitive fields that should never leave the service boundary.

For queries that must filter by a different attribute, include the user identifier as a condition to prevent cross-user reads:

app.get('/api/users/:userId/orders', async (req, res) => {
  const userId = req.user.sub;
  const requestedId = req.params.userId;
  if (userId !== requestedId) {
    return res.status(403).json({ error: 'forbidden' });
  }
  const params = {
    TableName: 'Orders',
    IndexName: 'UserIdIndex',
    KeyConditionExpression: 'userId = :uid',
    ExpressionAttributeValues: { ':uid': userId },
    ProjectionExpression: 'orderId,amount,timestamp'
  };
  const result = await dynamodb.query(params).promise();
  res.json(result.Items);
});

This pattern ensures that even if an attacker manipulates :userId, the server compares it to the authenticated subject and blocks unauthorized access.

When using scan-based operations, prefer query patterns with Global Secondary Indexes (GSIs) to avoid full-table scans and to enforce access controls at the partition key level. If scanning is unavoidable, add explicit ownership checks on each item:

app.get('/api/shared', async (req, res) => {
  const userId = req.user.sub;
  const params = {
    TableName: 'SharedItems',
    FilterExpression: 'contains(allowedUsers, :uid) AND attribute_exists(expiresAt)',
    ExpressionAttributeValues: { ':uid': userId },
    ProjectionExpression: 'itemId,title,expiresAt'
  };
  const result = await dynamodb.scan(params).promise();
  res.json(result.Items);
});

Finally, normalize responses to avoid exposing raw DynamoDB type markers. A lightweight serialization step helps prevent accidental exposure of metadata and ensures consistent handling of sensitive fields across clients.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

How does middleBrick detect data exposure risks in DynamoDB-backed Express APIs?
middleBrick runs security checks in parallel, including Data Exposure and Property Authorization. It correlates OpenAPI/Swagger specs (with full $ref resolution) against runtime behavior to identify missing authorization, overly broad queries, and unprotected sensitive fields without relying on internal infrastructure details.
Can the free plan be used to scan an Express API that uses DynamoDB?
Yes; the free plan provides 3 scans per month and supports any endpoint that responds to HTTP requests, including Express services backed by DynamoDB. It does not store credentials or require code changes.