Sandbox Escape in Express with Dynamodb
Sandbox Escape in Express with Dynamodb — how this specific combination creates or exposes the vulnerability
In an Express service that uses Amazon DynamoDB as a persistence layer, a sandbox escape typically occurs when an attacker who can influence data or query parameters can move from a constrained data access path to operations that affect broader system behavior or other tenants. This often maps to BOLA/IDOR and Property Authorization checks, where weak authorization on DynamoDB requests allows one user’s identity or partition key to be substituted for another’s. If the Express route does not enforce tenant or user boundaries on the primary key expressions used with DynamoDB, an attacker can issue a GetItem or Query with a different key and retrieve or enumerate data that should be isolated.
DynamoDB itself does not provide application-level tenant isolation; it enforces identity only at the AWS account and IAM level. Therefore, Express code must ensure that every DynamoDB request includes the correct partition key and, when relevant, sort key derived from the authenticated subject. A missing or unchecked user identifier in the request chain can be combined with DynamoDB’s flexible query patterns to read or scan items outside the intended scope. For example, an endpoint that uses a userId path parameter to build a KeyConditionExpression but fails to validate that the userId matches the authenticated subject enables horizontal privilege escalation across user records stored in the same table.
The interaction between Express routing, middleware, and DynamoDB client calls amplifies risk when the application uses shared or reused request-scoped objects. If Express middleware injects user context into a DynamoDB DocumentClient command without strict validation, and the command is later reused or modified, an attacker may be able to alter the command’s key condition or filter expression. This can lead to data exposure or, in configurations where low-level IAM permissions are broader than intended, to operations that affect other services or resources referenced indirectly by the item data. Because DynamoDB responses can contain metadata and attributes that reveal table structure or indexing patterns, improper error handling in Express can unintentionally assist enumeration.
These patterns are surfaced by the LLM/AI Security checks in middleBrick, which probe for system prompt leakage and injection paths, and by the BOLA/IDOR and Property Authorization checks that correlate runtime findings with OpenAPI/Swagger definitions, including $ref resolution across 2.0, 3.0, and 3.1 specs. The scanner maps findings to OWASP API Top 10 and compliance frameworks, highlighting where Express routes and DynamoDB access patterns fail to enforce least privilege and subject-bound authorization.
Dynamodb-Specific Remediation in Express — concrete code fixes
Remediation centers on enforcing strict ownership checks, validating input against the authenticated subject, and avoiding dynamic construction of key expressions that can be influenced by the client. Always derive the partition key and sort key from the authenticated user identity, and never allow raw user input to replace or partially override these values.
- Validate userId against the authenticated subject before using it in DynamoDB requests.
- Use parameterized key expressions and avoid concatenating user input into key condition strings.
- Apply fine-grained IAM policies so that the Express service principal can only access items belonging to the caller’s tenant or user scope.
The following example shows a secure Express route for retrieving a user profile from DynamoDB. It assumes an authenticated subject available on req.user with an identifier that must match the requested userId.
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
app.get('/api/users/:userId', async (req, res) => {
const authenticatedUserId = req.user.sub; // e.g., from JWT or session
const requestedUserId = req.params.userId;
if (authenticatedUserId !== requestedUserId) {
return res.status(403).json({ error: 'Forbidden: mismatched user identity' });
}
const params = {
TableName: process.env.USER_TABLE,
Key: {
userId: authenticatedUserId,
},
};
try {
const data = await dynamodb.get(params).promise();
if (!data.Item) {
return res.status(404).json({ error: 'Not found' });
}
res.json(data.Item);
} catch (err) {
res.status(500).json({ error: 'Internal error' });
}
});
For queries that involve a sort key, ensure the same ownership discipline. The next example retrieves a user’s posts by enforcing that the partition key equals the authenticated user and that the sort key conforms to expected patterns.
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
app.get('/api/users/:userId/posts', async (req, res) => {
const authenticatedUserId = req.user.sub;
const requestedUserId = req.params.userId;
if (authenticatedUserId !== requestedUserId) {
return res.status(403).json({ error: 'Forbidden: mismatched user identity' });
}
const params = {
TableName: process.env.POSTS_TABLE,
IndexName: 'UserIdIndex',
KeyConditionExpression: 'userId = :uid AND begins_with(sortKey, :skPrefix)',
ExpressionAttributeValues: {
':uid': authenticatedUserId,
':skPrefix': 'POST#' + requestedUserId,
},
};
try {
const result = await dynamodb.query(params).promise();
res.json(result.Items || []);
} catch (err) {
res.status(500).json({ error: 'Internal error' });
}
});
When using middleware to populate request-scoped DynamoDB parameters, ensure the values are derived from the authenticated identity and not directly from merged sources such as req.body, req.query, or req.params without validation. middleBrick’s Property Authorization and BOLA/IDOR checks are designed to detect when such mappings are missing or inconsistent with the declared security scheme, and its OpenAPI/Swagger analysis with full $ref resolution can highlight mismatches between spec definitions and runtime behavior.