Api Rate Abuse in Hapi with Dynamodb
Api Rate Abuse in Hapi with Dynamodb — how this specific combination creates or exposes the vulnerability
Rate abuse in a Hapi API backed by DynamoDB typically arises when request-rate controls are absent or misaligned with DynamoDB’s own limits. Without explicit rate limiting at the API layer, an unauthenticated or low-assurance client can issue many requests per second. Each request may perform DynamoDB operations such as GetItem, PutItem, or Query. If requests exceed DynamoDB provisioned capacity, this can manifest as HTTP 400 responses with ProvisionedThroughputExceeded or throttling errors, which may be unintentionally surfaced to clients and can aid an attacker in fingerprinting service behavior. More critically, if endpoints perform write operations or conditional checks without idempotency controls, an attacker can amplify load by repeating or slightly mutating requests, causing inflated DynamoDB read/write capacity consumption and increasing cost exposure. This pattern also intersects with BFLA/ privilege escalation risks when rate abuse is paired with weak authorization: a low-privilege account that lacks per-user rate constraints might be able to generate excessive DynamoDB activity on behalf of other users. Because DynamoDB does not natively enforce global request-rate policies per API key or token, the responsibility for rate control falls to the application layer (Hapi) or an external gateway. If Hapi routes do not validate caller identity and enforce tiered rate limits, the unauthenticated attack surface remains open, enabling techniques such as rapid account creation, token enumeration, or data scraping. These concerns are compounded when endpoints accept user-supplied keys or pagination tokens that can drive expensive DynamoDB Query scans, leading to inflated consumed capacity and potential service degradation. Overall, the combination of Hapi’s flexible routing and DynamoDB’s throughput model means that without deliberate rate-limiting and usage tracking, rate abuse can degrade reliability, increase costs, and expose sensitive operational patterns.
Dynamodb-Specific Remediation in Hapi — concrete code fixes
To mitigate rate abuse for Hapi services using DynamoDB, implement server-side rate limiting tied to authenticated or identifiable principals, and align DynamoDB operations with safe patterns. Below are concrete code examples using Node.js with Hapi and the AWS SDK for JavaScript (v3).
1. Basic rate limiting with a token-bucket in Hapi
Use a lightweight in-memory store for prototyping; for distributed systems, use Redis or DynamoDB itself to share state across instances.
// server.js
const Hapi = require('@hapi/hapi');
const { DynamoDBClient, GetItemCommand, PutItemCommand } = require('@aws-sdk/client-dynamodb');
const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute
const MAX_REQUESTS_PER_WINDOW = 100;
const bucket = new Map(); // key -> { count, resetAt }
function allowRequest(key) {
const now = Date.now();
const record = bucket.get(key);
if (!record) {
bucket.set(key, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS });
return true;
}
if (now >= record.resetAt) {
bucket.set(key, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS });
return true;
}
if (record.count < MAX_REQUESTS_PER_WINDOW) {
record.count += 1;
return true;
}
return false;
}
const init = async () => {
const server = Hapi.server({ port: 4000, host: 'localhost' });
server.ext('onRequest', (request, h) => {
const ip = request.info.remoteAddress;
// If authenticated, prefer a user-id or API-key based identifier
const key = request.auth.credentials ? request.auth.credentials.sub : ip;
if (!allowRequest(key)) {
return h.response({ message: 'Rate limit exceeded' }).code(429);
}
return h.continue;
});
server.route({
method: 'GET',
path: '/profile/{id}',
options: {
auth: false // or use auth strategy for real endpoints
},
handler: async (request) => {
const client = new DynamoDBClient({ region: 'us-east-1' });
const cmd = new GetItemCommand({
TableName: 'Profiles',
Key: { id: { S: request.params.id } }
});
const resp = await client.send(cmd);
return resp.Item;
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
init().catch(console.error);
2. Conditional writes with idempotency and capacity awareness
Use a client-side token to avoid duplicate processing and control write amplification against DynamoDB.
// handlers/submit.js
const { DynamoDBClient, PutItemCommand } = require('@aws-sdk/client-dynamodb');
const client = new DynamoDBClient({ region: 'us-east-1' });
exports.submitHandler = {
method: 'POST',
path: '/submit',
options: {
auth: false,
plugins: {
'hapi-rate-limit': {
limit: 30,
window: '1m' // example if using a plugin; otherwise use the custom bucket above
}
}
},
handler: async (request, h) => {
const { userId, idempotencyToken, data } = request.payload;
// Idempotency check using a dedicated DynamoDB table
const checkCmd = new GetItemCommand({
TableName: 'Idempotency',
Key: { token: { S: idempotencyToken } }
});
const existing = await client.send(checkCmd);
if (existing.Item) {
return { status: 'duplicate', originalResult: existing.Item.result };
}
// Write with conditional expression to avoid overwrites
const putCmd = new PutItemCommand({
TableName: 'Submissions',
Item: {
userId: { S: userId },
idempotencyToken: { S: idempotencyToken },
data: { S: JSON.stringify(data) },
createdAt: { S: new Date().toISOString() }
},
ConditionExpression: 'attribute_not_exists(idempotencyToken)'
});
try {
await client.send(putCmd);
// Record idempotency token with TTL to bound table growth
await client.send(new PutItemCommand({
TableName: 'Idempotency',
Item: {
token: { S: idempotencyToken },
result: { S: 'success' },
expiresAt: { N: (Date.now() + 7 * 24 * 3600 * 1000).toString() } // 7 days
}
}));
return { status: 'ok' };
} catch (err) {
if (err.name === 'ConditionalCheckFailedException') {
return { status: 'conflict' };
}
throw err;
}
}
};
3. Server-side usage notes
- DynamoDB
ProvisionedThroughputExceededandThrottledRequestsare runtime indicators; instrument these to tune your Hapi rate limits. - For production, replace the in-memory bucket with a shared store (e.g., Redis or a dedicated DynamoDB table with atomic counters) to coordinate limits across multiple Hapi instances.
- Combine per-identifier limits with global limits to protect DynamoDB write capacity units (WCU) and read capacity units (RCU) from burst traffic.