Distributed Denial Of Service in Fiber with Dynamodb
Distributed Denial Of Service in Fiber with Dynamodb — how this specific combination creates or exposes the vulnerability
A Distributed Denial of Service (DDoS) scenario in a Fiber application that uses DynamoDB can arise from how unbounded requests interact with database behavior and client-side retry logic. When a Fiber service issues frequent or poorly controlled requests to DynamoDB—such as scanning an unindexed attribute or querying with a non-partitioned filter—latency increases on the backend. If the client retries aggressively on errors or timeouts, the combination can amplify load on both the application and the database, contributing to availability impact.
DynamoDB-specific conditions that can exacerbate DDoS risk include:
- Provisioned capacity with insufficient read/write units for traffic bursts, leading to throttling responses that cause client backoff/retry storms.
- Hot partitions caused by skewed access patterns (for example, many requests targeting the same partition key), which increase consumed capacity and latency.
- Unindexed queries or complex condition expressions that consume additional read capacity and prolong request duration.
- Missing or misconfigured retry and timeout settings in the HTTP client, causing repeated attempts that increase concurrent load.
An observable pattern in Fiber routes could involve a high-volume endpoint that scans a DynamoDB table without pagination or rate-limiting, potentially exhausting provisioned throughput. Because DynamoDB responses include metrics such as ConsumedCapacity and ThrottledRequests, these can be correlated with application logs to detect patterns that precede availability degradation.
Dynamodb-Specific Remediation in Fiber — concrete code fixes
Remediation focuses on reducing request intensity, smoothing load, and ensuring efficient queries. Use pagination, conditional writes, and targeted queries with indexes; set timeouts and retries conservatively; and monitor consumed capacity to adjust provisioned throughput or switch to on-demand capacity for variable workloads.
Example: Safe DynamoDB query with context timeout and controlled pagination in Fiber
const { DynamoDB } = require("aws-sdk");
const dynamo = new DynamoDB({ region: "us-east-1" });
const { timeout } = require("promise-timeout");
app.get("/items", async (req, res) => {
const { status } = res;
const { category, limit = 20 } = req.query;
const maxRetries = 1;
const perTry = 5000;
try {
const safeQuery = timeout(dynamo.query({
TableName: process.env.TABLE_NAME,
KeyConditionExpression: "category = :cat",
ExpressionAttributeValues: {
":cat": { S: category },
},
Limit: Math.min(Number(limit), 100),
ConsistentRead: false,
}).promise(), perTry);
const data = await safeQuery;
if (!data || !data.Items || data.Items.length === 0) {
return status(200).send({ items: [] });
}
status(200).send({ items: data.Items });
} catch (err) {
if (err.name === "TimeoutError") {
return status(504).send({ error: "upstream timeout" });
}
if (err.code === "ProvisionedThroughputExceededException") {
return status(429).send({ error: "throughput exceeded, retry later" });
}
console.error("DynamoDB query error", err);
status(502).send({ error: "invalid response" });
}
});
Key practices:
- Set a reasonable timeout (e.g., 5–10 seconds) to avoid hanging connections that tie up resources.
- Use pagination with Limit and ExclusiveStartKey instead of scanning entire tables.
- Prefer Query with an index over Scan; create Global Secondary Indexes (GSI) for common access patterns.
- Implement idempotent writes with ConditionExpression to avoid duplicate operations that waste capacity.
- Handle ThrottledRequests gracefully: exponential backoff with jitter and a bounded retry count.
- Monitor CloudWatch metrics (ConsumedReadCapacityUnits, ConsumedWriteCapacityUnits, ThrottledRequests) and consider on-demand capacity or auto-scaling if spikes are common.
Example: Conditional write to prevent overwrite storms
app.post("/reserve", async (req, res) => {
const { itemId, expectedStatus } = req.body;
try {
const result = await dynamo.update({
TableName: process.env.TABLE_NAME,
Key: { itemId: { S: itemId } },
UpdateExpression: "set #status = :newstatus",
ConditionExpression: "#status = :expected",
ExpressionAttributeNames: { "#status": "status" },
ExpressionAttributeValues: {
":newstatus": { S: "reserved" },
":expected": { S: expectedStatus },
},
}).promise();
res.send({ success: true, item: result.Attributes });
} catch (err) {
if (err.code === "ConditionalCheckFailedException") {
return res.status(409).send({ error: "already reserved or changed" });
}
res.status(502).send({ error: "failed to reserve" });
}
});