Denial Of Service in Feathersjs with Dynamodb
Denial Of Service in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework for building JavaScript APIs and real-time applications. When FeathersJS services are backed by DynamoDB, certain application-level patterns can create denial-of-service (DoS) risks even though DynamoDB itself is a managed service. DoS in this combination typically arises from unbounded queries, missing pagination, tight loops that generate excessive request volume, and missing client-side guards that cause repeated or amplified requests to DynamoDB.
One common pattern is a service that queries DynamoDB without server-side pagination or limit enforcement. If a client requests a list without specifying $limit or if the service defaults to scanning a large table segment, the runtime can generate many strongly consistent reads or exhaust memory while assembling large result sets. In a FeathersJS hook or service method, failing to enforce a reasonable cap on params.query.$limit or not applying a default cap enables an authenticated or unauthenticated attacker to force heavy read usage, increasing cost and latency and potentially triggering AWS account-side throttling or errors that manifest as a DoS to legitimate users.
Another DoS vector is the N+1 query pattern in nested service calls. For example, a FeathersJS service might retrieve a list of items from DynamoDB and then, for each item, perform an additional get or query to fetch related data. If an endpoint returns hundreds of rows and each row triggers an extra DynamoDB operation, the total request volume can spike quickly, consuming Lambda concurrency or backend compute and causing timeouts or degraded response times. This pattern often appears when developers use FeathersJS hooks or models to enrich data without batching or caching, inadvertently turning a single logical request into many small DynamoDB calls.
Real-world attack scenarios mirror standard OWASP API Top 10 risks such as excessive data exposure and broken function-level authorization, but the DoS impact manifests through consumed read capacity, Lambda duration, and connection pool exhaustion. For instance, an unauthenticated endpoint that accepts arbitrary filter criteria without input validation can allow an attacker to submit deeply nested or inefficient queries that scan large portions of a DynamoDB table. Similarly, missing rate limiting at the FeathersJS layer can allow rapid, repeated calls that amplify the load on DynamoDB. Because DynamoDB does not block requests by default, the service must enforce sensible limits, batch operations, and validate inputs to avoid creating a self-inflicted DoS condition.
Dynamodb-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on bounding requests, batching where possible, validating inputs, and applying sensible defaults in FeathersJS services that use DynamoDB. The following examples show concrete, working patterns.
1. Enforce pagination and limits in service params
Ensure every query to DynamoDB respects a maximum page size. In a FeathersJS service, read params.query and coerce or cap $limit before building the DynamoDB request.
const { DynamoDBDocumentClient, QueryCommand } = require("@aws-sdk/lib-dynamodb");
const ddbDocClient = DynamoDBDocumentClient.from(client);
class TodoService {
constructor({ table, maxLimit = 50 }) {
this.table = table;
this.maxLimit = maxLimit;
}
async find(params) {
const query = params.query || {};
const limit = Math.min(
Number(query.$limit) || 10,
this.maxLimit
);
const lastKey = query.$lastKey ? JSON.parse(query.$lastKey) : undefined;
const command = new QueryCommand({
TableName: this.table,
KeyConditionExpression: "userId = :uid",
ExpressionAttributeValues: {
":uid": params.user.id,
},
Limit: limit,
ExclusiveStartKey: lastKey || undefined,
});
const { Items, LastEvaluatedKey } = await ddbDocClient.send(command);
return {
total: Items.length,
limit,
skip: 0,
data: Items,
...(LastEvaluatedKey && { next: { $lastKey: JSON.stringify(LastEvaluatedKey) } }),
};
}
}
module.exports = function (app) {
app.use("/todos", new TodoService({ table: process.env.TODO_TABLE }));
};
2. Batch reads to avoid N+1 queries
Replace per-item get calls with a batch get using BatchGetCommand. In a Feathers hook or a custom method, collect IDs and fetch them in a single DynamoDB operation.
const { DynamoDBDocumentClient, BatchGetCommand } = require("@aws-sdk/lib-dynamodb");
const ddbDocClient = DynamoDBDocumentClient.from(client);
async function getRelatedItems(ids) {
const command = new BatchGetCommand({
RequestItems: {
[process.env.RELATED_TABLE]: {
Keys: ids.map((id) => ({ id })),
},
},
});
const { Responses } = await ddbDocClient.send(command);
return Responses[process.env.RELATED_TABLE] || [];
}
// Usage in a Feathers before hook
app.service("items").hooks({
before: {
async find(context) {
const ids = context.result.data.map((r) => r.relatedId);
const related = await getRelatedItems(ids);
const map = new Map(related.map((r) => [r.id, r]));
context.result.data = context.result.data.map((row) => ({
...row,
relatedData: map.get(row.relatedId) || null,
}));
return context;
},
},
});
3. Validate and sanitize query inputs
Reject or sanitize filter values that could trigger large scans. For queries that use a filter on non-key attributes, enforce allowlists and reject overly broad expressions that could scan the table.
app.service("reports").hooks({
before: {
find(context) {
const query = context.params.query || {};
if (query.status && !["open", "closed", "pending"].includes(query.status)) {
throw new Error("Invalid status filter");
}
if (query.$limit && Number(query.$limit) > 100) {
query.$limit = 100;
}
if (query.$sort) {
// Only allow sorting on indexed fields
const allowedSort = ["createdAt", "updatedAt"];
const sortField = query.$sort.split(":")[0];
if (!allowedSort.includes(sortField)) {
throw new Error("Sorting on this field is not allowed");
}
}
return context;
},
},
});
4. Apply rate limiting and circuit-breaker patterns at the Feathers layer
Use in-memory or external rate limiting to prevent bursts that amplify DynamoDB load. While FeathersJS does not include built-in rate limiting, you can integrate a lightweight middleware or use package-level guards to limit requests per user/IP within a sliding window.
const rateLimit = new Map();
function rateLimitMiddleware(max = 30, windowMs = 60_000) {
return function (context, next) {
const user = context.params.user?.id || context.params.ip || "anonymous";
const now = Date.now();
const entry = rateLimit.get(user) || { count: 0, reset: now + windowMs };
if (now > entry.reset) {
entry.count = 0;
entry.reset = now + windowMs;
}
entry.count += 1;
if (entry.count > max) {
const err = new Error("Too many requests");
err.status = 429;
throw err;
}
rateLimit.set(user, entry);
return next();
};
}
app.use(rateLimitMiddleware(30, 60_000));
5. Use DynamoDB features to reduce fan-out load
Design your table and queries to minimize repeated scans. Use sparse indexes, projection expressions to return only required attributes, and DAX or caching for hot keys. In FeathersJS, prefer selective projections via params.query.$select and avoid returning every attribute by default.
app.service("messages").find({
query: {
$select: "messageId,content,timestamp",
$limit: 20,
userId: "user-123",
},
});Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |