Bola Idor in Feathersjs with Dynamodb
Bola Idor in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability
BOLA (Broken Level of Authorization) / IDOR occurs when an API exposes one user’s resource through an identifier that should be isolated to another. In a Feathersjs service backed by DynamoDB, this commonly arises when the service exposes a generic find/update/remove endpoint and relies only on the client-supplied id without scoping the request to the authenticated subject’s tenant or user partition key. Because DynamoDB queries are defined by key expressions, an unsafe query can return items that share a partition key but should be restricted further by a sort key or record ownership field.
Consider a multi-tenant application where each tenant owns a set of configurations. A Feathers service might define a DynamoDB table with tenantId as the partition key and configName as the sort key. If the service implements a get method like get(id, params) and passes id directly to a DynamoDB get or query without ensuring that the resolved id belongs to the requester’s tenant, an authenticated user can enumerate or modify other tenants’ configurations simply by guessing or iterating IDs.
Feathers generates hooks that transform calls into DynamoDB expressions. If those hooks do not inject tenant context into the key condition, the service performs an unbounded or over-permissive query. For example, a find hook that builds a KeyConditionExpression from user input without enforcing tenantId = :tid allows horizontal privilege escalation across records sharing the same partition key. Real-world patterns include using Cognito identity IDs as partition keys; if the service does not validate that the requested item’s partition key matches the authenticated identity’s partition key, IDOR is trivially reachable.
Additionally, unsafe use of DynamoDB update with a client-supplied key can lead to BOLA when the update expression does not reassert ownership. An attacker who knows another user’s ID can craft a PATCH request to modify that record if the service does not re-check tenant or user ownership in the update path. Because DynamoDB returns errors only for missing keys rather than authorization failures, the service may mistakenly interpret a missing item as a successful no-op, masking the violation.
The interaction of Feathers’ flexible hooks and DynamoDB’s key-based model means developers must explicitly bind the authenticated subject to the key schema. Without this binding, the API surface implicitly trusts the client identifier, which is the root cause of BOLA/IDOR in this stack.
Dynamodb-Specific Remediation in Feathersjs — concrete code fixes
Remediation centers on enforcing ownership at the DynamoDB query level and ensuring hooks inject immutable context derived from the authenticated subject. Below are concrete patterns using the AWS SDK for JavaScript (v3) with Feathers hooks.
1. Scoped query with tenant partition key
Ensure every query includes the tenant identifier derived from the authenticated user. Do not rely on the client-provided ID alone.
const { DynamoDBClient, GetItemCommand, QueryCommand } = require("@aws-sdk/client-dynamodb");
const client = new DynamoDBClient({ region: "us-east-1" });
app.service("configs").hooks({
before: {
async get(id, params) {
const { user } = params;
if (!user || !user.tenantId) throw new Error("Unauthenticated");
params.sequelizeWhere = {
tenantId: { [Op.eq]: user.tenantId },
configName: { [Op.eq]: id }
};
return params;
},
async find(params) {
const { user } = params;
if (!user || !user.tenantId) throw new Error("Unauthenticated");
params.sequelizeWhere = {
tenantId: { [Op.eq]: user.tenantId }
};
return params;
}
}
});
2. Parameterized KeyConditionExpression in a custom handler
If using a low-level DynamoDB adapter, bind the partition key explicitly and avoid string concatenation.
const { DynamoDBClient, QueryCommand } = require("@aws-sdk/client-dynamodb");
const client = new DynamoDBClient({ region: "us-east-1" });
async function getScopedConfig(tenantId, configName) {
const command = new QueryCommand({
TableName: process.env.CONFIG_TABLE,
KeyConditionExpression: "tenantId = :tid AND configName = :cname",
ExpressionAttributeValues: {
":tid": { S: tenantId },
":cname": { S: configName }
}
});
const response = await client.send(command);
return response.Items;
}
3. Update with ownership re-verification
For update/remove operations, re-assert the tenant-key in the condition expression to prevent unauthorized writes.
const { DynamoDBClient, UpdateItemCommand } = require("@aws-sdk/client-dynamodb");
const client = new DynamoDBClient({ region: "us-east-1" });
async function safeUpdateConfig(tenantId, configName, updateExpression, expressionAttributeValues) {
const command = new UpdateItemCommand({
TableName: process.env.CONFIG_TABLE,
Key: {
tenantId: { S: tenantId },
configName: { S: configName }
},
UpdateExpression: updateExpression,
ExpressionAttributeValues: expressionAttributeValues,
ConditionExpression: "tenantId = :tid AND configName = :cname",
ExpressionAttributeValues: {
...expressionAttributeValues,
":tid": { S: tenantId },
":cname": { S: configName }
}
});
await client.send(command);
}
4. Feathers middleware to inject user context
Use a custom hook to attach tenant-aware parameters before the service handler runs.
const authHook = (context) => {
const { user } = context.params;
if (user && user.tenantId) {
context.params.tenantId = user.tenantId;
} else {
throw new Error("Missing tenant context");
}
return context;
};
app.service("records").hooks({
before: {
all: [authHook],
find: [
(context) => {
const { tenantId } = context.params;
context.params.tenantId = tenantId;
return context;
}
]
}
});
5. Avoid client-controlled key expressions
Do not construct DynamoDB expressions from raw IDs. Always map the client ID to a scoped key using server-side logic that includes the authenticated subject’s partition key.
6. Validation and error handling hygiene
Return generic not-found messages to avoid leaking existence information, and log authorization anomalies without exposing tenant identifiers in responses.
These patterns align with OWASP API Top 10 A01:2023 broken object-level authorization and reduce the likelihood of IDOR by binding every DynamoDB operation to the authenticated subject’s tenant or user context.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |