Race Condition in Fastapi with Dynamodb
Race Condition in Fastapi with Dynamodb — how this specific combination creates or exposes the vulnerability
A race condition in a FastAPI service that uses DynamoDB typically occurs when multiple concurrent requests read and write the same item without adequate synchronization, leading to lost updates or invalid state transitions. In serverless or high-concurrency deployments, two or more FastAPI endpoints may execute overlapping read-modify-write sequences on a DynamoDB item. For example, an endpoint that reads an item’s current counter, increments it locally, and then writes back the new value is vulnerable: if two requests read the same initial value before either writes, both will compute the same incremented result and overwrite each other, effectively losing one update.
DynamoDB’s default isolation model is eventually consistent reads for strongly consistent reads you must explicitly request; however, even with strongly consistent reads, the window between read and write in application code remains exposed. Consider a FastAPI route that retrieves an item’s inventory count, checks availability, and then updates the item. An attacker can trigger parallel requests to consume the last available unit multiple times before the write occurs. This pattern maps to common OWASP API Top 10 risks such as Broken Object Level Authorization (BOLA) when the race modifies authorization-sensitive state, and can be surfaced by middleBrick’s BOLA/IDOR and Property Authorization checks, which correlate spec definitions with runtime behavior to identify unsafe state transitions.
DynamoDB conditional writes are designed to prevent this class of issue, but only when used correctly. If the FastAPI logic omits the ConditionExpression, or uses an incorrect attribute version, the write will succeed regardless of concurrent changes, creating a classic lost update. Additionally, patterns that implement application-level locking or compare-and-set without leveraging DynamoDB’s native mechanisms increase latency and error surface. MiddleBrick’s scans for BFLA/Privilege Escalation and Rate Limiting can highlight missing safeguards like missing idempotency keys or insufficient concurrency controls, while the Input Validation checks ensure that ETags or version attributes are properly validated before being used in conditional expressions.
In architectures that integrate DynamoDB streams and Lambda triggers, race conditions can propagate into downstream processing, where out-of-order events cause inconsistent materialized views. FastAPI endpoints that produce writes without idempotency tokens or unique request identifiers may see duplicate processing when streams are replayed or when clients retry on unobserved network errors. The scan’s Unsafe Consumption and Inventory Management checks examine how your API manages state transitions and whether operations are safely idempotent. Using middleBrick’s OpenAPI/Swagger analysis with full $ref resolution, the tool cross-references declared concurrency controls against actual runtime tests, surfacing missing optimistic locking or missing version attributes in the item model.
Dynamodb-Specific Remediation in Fastapi — concrete code fixes
To remediate race conditions in FastAPI with DynamoDB, enforce atomic updates using conditional writes and version attributes. Represent each item with a version number or timestamp, and include a ConditionExpression in every write that must match the value read. This ensures the write only succeeds if no other request has mutated the item since the read, causing concurrent conflicting writes to fail with a ConditionalCheckFailedException that FastAPI can catch and handle appropriately.
import boto3
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
app = FastAPI()
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('items')
class ItemUpdate(BaseModel):
quantity: int
@app.put("/items/{item_id}")
def update_item_quantity(item_id: str, update: ItemUpdate):
# Read with consistent read to get latest version
response = table.get_item(Key={'item_id': item_id}, ConsistentRead=True)
item = response.get('Item')
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
expected_version = item['version']
new_quantity = update.quantity
# Atomic conditional write: only succeed if version matches
try:
table.put_item(
Item={
'item_id': item_id,
'quantity': new_quantity,
'version': expected_version + 1
},
ConditionExpression='attribute_exists(item_id) AND version = :expected_version',
ExpressionAttributeValues={':expected_version': expected_version}
)
except table.meta.client.exceptions.ConditionalCheckFailedException:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Item was modified concurrently, please retry"
)
return {"item_id": item_id, "quantity": new_quantity, "version": expected_version + 1}
For operations that require increments, use DynamoDB’s ADD action with a versioned attribute to avoid read-before-write patterns entirely. This shifts the synchronization into the database and removes the race window. If you must read-then-act, wrap the sequence in a transaction with explicit locking semantics or use a dedicated lock item with TTL, but prefer atomic increment operations where possible.
def safe_increment(item_id: str, delta: int = 1):
# Atomic increment without reading first; returns updated value
response = table.update_item(
Key={'item_id': item_id},
UpdateExpression='ADD quantity :delta SET version = if_not_exists(version, :zero) + :one',
ConditionExpression='attribute_exists(item_id)',
ExpressionAttributeValues={':delta': delta, ':zero': 0, ':one': 1},
ReturnValues='UPDATED_NEW'
)
return response['Attributes']
FastAPI endpoints should also implement idempotency keys for mutating requests, storing the key in DynamoDB with the item version to ensure retries do not cause duplicate side effects. Combine this with robust error handling for ConditionalCheckFailedException and appropriate retry logic on the client. middleBrick’s CLI tool can be run as middlebrick scan <url> to validate that your endpoints include version checks and conditional writes, while the GitHub Action can enforce that new API changes do not introduce missing optimistic locking. The MCP Server allows you to scan APIs directly from your AI coding assistant to catch missing atomic patterns during development.