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