Time Of Check Time Of Use in Express with Dynamodb
Time Of Check Time Of Use in Express with Dynamodb — how this specific combination creates or exposes the vulnerability
Time of Check to Time of Use (TOCTOU) occurs when the state of a resource changes between an authorization check and the subsequent use of that resource. In an Express application using DynamoDB, this commonly appears in workflows where a read-based check (e.g., verifying ownership or permissions) is performed, and later a write action (e.g., update or delete) is executed without re-validating the current state under concurrency or mutation pressures.
Consider an endpoint that first retrieves an item to confirm the user has permission, then uses that item’s identifier to perform a conditional update. If the item is modified by another process or an attacker between the read and the write, the original check no longer reflects reality. In DynamoDB, conditional writes help mitigate this, but only when consistently applied. Without them, the window between check and use enables scenarios such as tampered object references or privilege escalation across user boundaries (BOLA/IDOR).
For example, a route like /users/:id/profile might first fetch a profile to ensure the requesting user matches the profile owner, then proceed to update it. If the check uses a simple read without a conditional write, an attacker who can manipulate identifiers or exploit race conditions can update another user’s profile. This maps to OWASP API Top 10:5 — Broken Object Level Authorization. DynamoDB’s strongly consistent reads reduce but do not eliminate risk if the application logic does not enforce checks within the write itself.
Using the scan capabilities of middleBrick against such endpoints can surface these authorization gaps by correlating unauthenticated behavior patterns with the API specification. Findings include missing conditional expressions and unchecked state changes that enable TOCTOU classes of vulnerabilities.
Dynamodb-Specific Remediation in Express — concrete code fixes
Remediation centers on performing authorization within the write operation using DynamoDB conditional expressions, avoiding reliance on pre-check state. Always use conditional writes (e.g., ConditionExpression) to ensure the item’s state matches expectations at the moment of update or delete. Combine this with ownership checks embedded in the key schema or secondary indexes to enforce BOLA-style protections.
Example: Safe profile update with conditional write
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
exports.updateProfile = async (req, res) => {
const { userId } = req.params;
const { email, phone } = req.body;
const params = {
TableName: 'UserProfiles',
Key: { userId },
UpdateExpression: 'set email = :email, phone = :phone',
ConditionExpression: 'userId = :uid', // Ensures item belongs to user at write time
ExpressionAttributeValues: {
':email': email,
':phone': phone,
':uid': userId
},
ReturnValues: 'UPDATED_NEW'
};
try {
const data = await dynamo.update(params).promise();
res.json({ success: true, data });
} catch (err) {
if (err.code === 'ConditionalCheckFailedException') {
return res.status(403).json({ error: 'Unauthorized or item changed' });
}
res.status(500).json({ error: 'Internal server error' });
}
};
Example: Delete with ownership check in the key
Design your table so that ownership is part of the primary key (e.g., PK = USER#userId, SK = PROFILE#profileId). This enables efficient deletes scoped to the user without a prior read:
const params = {
TableName: 'UserProfiles',
Key: { userId, profileId },
ConditionExpression: 'attribute_exists(userId) AND userId = :uid',
ExpressionAttributeValues: { ':uid': userId }
};
await dynamo.delete(params).promise();
Example: Using middleBrick for continuous verification
Integrate the middleBrick CLI or GitHub Action to validate that conditional expressions exist on write operations and that scan findings related to BOLA/IDOR are addressed. The dashboard can track changes over time, and the MCP server allows AI coding assistants to surface insecure patterns during development.
General best practices
- Use conditional writes for every write that depends on prior read state.
- Embed ownership in keys or use GSIs to enforce scoping without extra reads.
- Avoid read-before-write patterns unless strictly necessary and paired with strong conditional checks.
- Enable DynamoDB Streams cautiously and treat them as event sources that still require authorization on consumption.