Timing Attack in Feathersjs with Dynamodb
Timing Attack in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability
A timing attack in a Feathersjs service that uses DynamoDB can occur when authentication or lookup logic branches based on whether a record exists, and the difference in execution time is measurable over the network. For example, a login or token verification flow that performs a get or query against DynamoDB and then conditionally performs extra work only when an item is found can leak existence information via response time differences. An attacker who can send many crafted requests and observe timing can infer valid user IDs or API keys without needing to understand internal implementation details.
Feathersjs typically interacts with DynamoDB through a custom service adapter or hook. If the adapter performs a conditional check such as if (record) { transform or enrich } only when DynamoDB returns an item, the branch introduces timing variability. This is especially relevant when combined with cryptographic operations or normalization logic that only runs on existing records. Network jitter must be controlled experimentally to confirm that timing differences are caused by application logic rather than infrastructure variability. In a black-box scan, such timing behavior can be observed as inconsistent response times correlated with record existence, which may be surfaced as a BOLA/IDOR or Authentication finding depending on context.
DynamoDB’s behavior also contributes to the risk profile. A GetItem on a non-existent key with a non-existent key condition returns a quick empty response with low latency, whereas a Query with a filter that does not match many items may take longer due to consumed read capacity and internal pagination work. In Feathersjs, if your service uses find with a non-selective query filter and then iterates over results to locate a specific entry, the timing can vary based on result set size and consumed capacity. These interactions between Feathersjs routing/hooks and DynamoDB request patterns create observable timing channels that an attacker can exploit to infer data presence, violating principles of constant-time handling for security-sensitive operations.
Dynamodb-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on ensuring that operations that depend on existence or ownership take constant time regardless of data presence, and that DynamoDB requests are structured to minimize timing variability. Below are concrete patterns for Feathersjs services.
Use consistent-time existence checks with ConditionExpression and ReturnValues
When verifying ownership or existence, use a condition that always consumes equivalent read capacity and avoid branching on the presence of an item. For example, use a conditional update with ReturnValues to perform a no-op write when the item does not belong to the caller, ensuring a stable response shape and duration.
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
async function checkOwnershipConstantTime(userId, itemId) {
const params = {
TableName: 'user_items',
Key: { itemId },
ConditionExpression: 'userId = :uid',
ExpressionAttributeValues: { ':uid': userId },
ReturnValues: 'NONE'
};
try {
await dynamodb.update(params).promise();
return true;
} catch (err) {
// ConditionalCheckFailedException or ProvisionedThroughputExceededException
// Handle generically to keep timing similar
return false;
}
}
Use Query with consistent pagination and limit, avoiding early exits
If you must search, request a fixed page size and process results in a uniform way. Avoid returning as soon as a match is found. Instead, iterate fully and use a constant-time comparison pattern.
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
async function findUserItemStableTime(userId, limit = 20) {
const params = {
TableName: 'user_items',
IndexName: 'userId-index',
KeyConditionExpression: 'userId = :uid',
ExpressionAttributeValues: { ':uid': userId },
Limit: limit
};
const result = await dynamodb.query(params).promise();
// Normalize: always iterate fully even if we already found a candidate
let found = null;
for (const item of result.Items || []) {
// Perform constant-time work (e.g., hash comparison) to avoid branching on existence
if (item.itemId === expectedItemId) {
found = item;
}
}
return found;
}
Leverage DynamoDB Transactions for atomic checks
Use TransactGetItems or TransactWriteItems to combine reads and writes atomically, reducing timing variability caused by network retries or conditional check failures. In Feathersjs, wrap transaction logic in a service method that returns a stable response shape.
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
async function atomicCheckAndUpdate(userId, itemId) {
const params = {
TransactItems: [
{
Get: {
TableName: 'user_items',
Key: { itemId },
ConsistentRead: true
}
},
{
Update: {
TableName: 'audit_log',
Key: { logId: `check-${Date.now()}` },
UpdateExpression: 'SET checked = :val',
ExpressionAttributeValues: { ':val': true }
}
}
]
};
try {
const out = await dynamodb.transactGetItems(params).promise();
// Process out.Responses consistently
return out.Responses;
} catch (err) {
// Handle generically
return null;
}
}
Apply middleware that normalizes timing in Feathers hooks
In Feathers, add a hook that performs constant-time work for authentication-related paths. For example, always run a dummy DynamoDB operation when a token is missing or invalid, so response times remain similar.
const { v4: uuidv4 } = require('uuid');
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
function timingSafeHook(options = {}) {
return async context => {
const { user } = context.params || {};
if (!user || !user.token) {
// Perform a constant-time no-op against DynamoDB to mask timing differences
await dynamodb.get({
TableName: 'dummy_table',
Key: { id: 'placeholder' }
}).promise();
throw new Error('Authentication required');
}
// Continue with normal verification
return context;
};
}
// Usage in a Feathers service
app.use('/secure', {
async before(hook) {
return timingSafeHook()(hook);
},
// ... other hook configurations
});
Ensure consistent encryption and payload sizes
When returning sensitive data, encrypt and pad responses to a consistent size to prevent leaking information via length differences. Use DynamoDB to store only necessary metadata and perform constant-time transformations in Feathersjs before sending to the client.
const crypto = require('crypto');
function constantTimeResponse(data, key) {
const json = JSON.stringify(data);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(json, 'utf8', 'hex');
encrypted += cipher.final('hex');
// Pad to a fixed block multiple to avoid length leaks
const blockSize = 16;
const padLen = blockSize - (encrypted.length % blockSize);
encrypted += '='.repeat(padLen);
return { iv: iv.toString('hex'), content: encrypted };
}