Integrity Failures in Dynamodb
How Integrity Failures Manifest in DynamoDB
Integrity failures in DynamoDB usually appear as lost updates, write skew, or phantom reads when application code assumes that a read‑modify‑write sequence is atomic but does not enforce it with conditional checks or transactions. A common pattern is a service that decrements a numeric attribute (e.g., a gift‑card balance) by first reading the item, calculating the new value, and then writing it back with a plain PutItem or UpdateItem call that lacks a ConditionExpression. If two attackers send concurrent requests, both may read the same stale value, compute the same new value, and overwrite each other’s result, causing the balance to drop more than intended or even become negative.
Another manifestation occurs when developers rely on BatchWriteItem for bulk updates without checking that each item still meets business rules. An attacker can inject a malformed item that passes validation but violates a cross‑item invariant (e.g., total allocated capacity across partitions must stay below a quota). Because the batch operation does not provide atomicity across items, the invariant can be broken silently.
Finally, misuse of DynamoDB Streams can lead to duplicate processing. If a Lambda function triggered by a stream does not deduplicate using an idempotency token or a processed‑record table, replaying the same stream record (which can happen during network retries) may apply the same business logic twice, inflating counters or issuing duplicate refunds.
DynamoDB-Specific Remediation
Fixing integrity issues in DynamoDB relies on the service’s native consistency mechanisms: conditional writes, transactions, and idempotency patterns.
Conditional writes for single‑item updates. Instead of a plain UpdateItem, include a ConditionExpression that validates the expected state before applying the change. Using the AWS SDK for JavaScript v3:
import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
const client = new DynamoDBClient({});
export async function redeemGiftCard(cardId, amount) {
const params = {
TableName: "GiftCards",
Key: { id: { S: cardId } },
UpdateExpression: "SET balance = balance - :amt",
ConditionExpression: "balance >= :amt",
ExpressionAttributeValues: { ":amt": { N: amount.toString() } },
ReturnValues: "ALL_NEW"
};
const command = new UpdateItemCommand(params);
return await client.send(command);
}
If another request tries to redeem more than the available balance, the condition fails and DynamoDB returns a ConditionalCheckFailedException, preventing the lost‑update scenario.
Transactional writes for multi‑item invariants. When an operation must update several items atomically (e.g., deducting from a gift‑card balance and incrementing a merchant’s sales total), wrap them in a TransactWriteItems call:
import { DynamoDBClient, TransactWriteItemsCommand } from "@aws-sdk/client-dynamodb";
const client = new DynamoDBClient({});
export async function purchaseWithGiftCard(cardId, merchantId, amount) {
const params = {
TransactItems: [
{
Update: {
TableName: "GiftCards",
Key: { id: { S: cardId } },
UpdateExpression: "SET balance = balance - :amt",
ConditionExpression: "balance >= :amt",
ExpressionAttributeValues: { ":amt": { N: amount.toString() } }
}
},
{
Update: {
TableName: "MerchantSales",
Key: { id: { S: merchantId } },
UpdateExpression: "SET total = total + :amt",
ExpressionAttributeValues: { ":amt": { N: amount.toString() } }
}
}
]
};
const command = new TransactWriteItemsCommand(params);
return await client.send(command);
}
If any condition fails, the entire transaction is rolled back, preserving cross‑item invariants.
Idempotency for Stream‑driven processors. In a Lambda function triggered by DynamoDB Streams, store a record of processed stream identifiers (e.g., the SequenceNumber) in a deduplication table, or use an idempotency token passed in the payload:
exports.handler = async (event) => {
for (const record of event.Records) {
const seq = record.dynamodb.SequenceNumber;
const already = await getItem({ TableName: "ProcessedEvents", Key: { seq: { S: seq } } });
if (already.Item) continue; // already handled
// process the change …
await putItem({ TableName: "ProcessedEvents", Item: { seq: { S: seq } } });
}
};
By combining these patterns—conditional checks for single‑item safety, transactions for multi‑item safety, and idempotency for stream processing—you eliminate the integrity failure modes that middleBrick detects.
Frequently Asked Questions
How does middleBrick detect missing integrity controls without any credentials or agents?
Can I enforce integrity checks across multiple DynamoDB tables using middleBrick’s findings?
TransactWriteItems operation, which provides atomicity across tables or within a table.