Insecure Direct Object Reference in Adonisjs with Dynamodb
Insecure Direct Object Reference in Adonisjs with Dynamodb — how this specific combination creates or exposes the vulnerability
In AdonisJS applications that use DynamoDB as the persistence layer, an Insecure Direct Object Reference (IDOR) occurs when an endpoint exposes a DynamoDB key or record identifier derived from user input without proper authorization checks. Because DynamoDB table keys (partition key and optional sort key) often map directly to resource identifiers, referencing a record by its key without validating that the authenticated subject has permission to access it becomes a classic IDOR vector.
Consider a route like GET /api/todos/:todoId where :todoId is used as a DynamoDB key (e.g., todoId). If the controller retrieves the item directly using the provided ID and returns it without confirming the item’s owner or tenant belongs to the requesting user, the unauthenticated or low-privilege attacker can enumerate valid IDs and read other users’ data. This risk is amplified when the ID is a predictable integer or UUID derived from a user’s own records, and no authorization guard is applied before the DynamoDB get call.
In a typical AdonisJS controller, the vulnerability appears as direct use of request parameters in DynamoDB operations. For example, using the AWS SDK for JavaScript v3, a controller might call get with a bare Key built from req.params.todoId and the table name. Because DynamoDB does not enforce row-level ownership automatically, the onus is on the application to ensure that each request is scoped to the requesting user’s allowed resources. Without such scoping — for instance, by checking that the record’s userId attribute matches the authenticated user’s ID — the endpoint leaks data across users, which middleBrick flags as a BOLA/IDOR finding with severity high in its parallel security checks.
OpenAPI specifications can inadvertently reinforce this risk if path parameters and responses expose internal key structures without clarifying authorization requirements. When combined with unauthenticated LLM endpoint detection and active prompt injection testing, middleBrick’s LLM/AI Security checks can identify whether API documentation or server hints reveal object references that facilitate enumeration. The scanner’s runtime tests then validate whether authorization boundaries are enforced, ensuring that IDOR is caught before attackers can chain IDOR with other techniques such as IDOR-driven data exfiltration.
Because DynamoDB is schemaless at the access layer, developers must enforce authorization in application code. An attacker does not need to understand internal implementation details; they only need to observe that changing an ID yields different data. middleBrick’s property authorization checks look for missing ownership or tenant filters in the request-to-DynamoDB flow, and its input validation checks ensure IDs conform to expected formats to prevent tampering. Remediation centers on binding every DynamoDB read or write to the authenticated subject and applying least-privilege IAM policies so that even if an ID is guessed, the caller cannot access it.
Dynamodb-Specific Remediation in Adonisjs — concrete code fixes
To remediate IDOR in AdonisJS with DynamoDB, always scope queries to the authenticated user and validate ownership before performing any DynamoDB operation. Below are concrete, syntactically correct examples using the AWS SDK for JavaScript v3 within an AdonisJS controller.
1. Scoped GET by authenticated subject
Instead of using the client-supplied ID directly, combine it with the authenticated user’s ID to construct a composite key or enforce a filter. This example shows how to retrieve a todo item only when the userId attribute matches the authenticated subject.
import { DynamoDBClient, GetCommand } from "@aws-sdk/client-dynamodb";
import { unmarshall } from "@aws-sdk/util-dynamodb";
const client = new DynamoDBClient({ region: "us-east-1" });
export default class TodosController {
async show({ params, auth }) {
const userId = auth.user.id; // authenticated subject
const todoId = params.id;
const command = new GetCommand({
TableName: process.env.DYNAMODB_TODOS_TABLE,
Key: {
userId: { S: userId },
todoId: { S: todoId },
},
});
const response = await client.send(command);
if (!response.Item) {
return null;
}
return unmarshall(response.Item);
}
}
This ensures that even if an attacker guesses or iterates todoId, they cannot access items belonging to other users because the partition key includes userId. The table design should use a composite primary key where the partition key is the user ID and the sort key is the todo ID, which aligns with DynamoDB best practices for ownership and isolation.
2. List with ownership filter and pagination
When listing resources, always filter by the authenticated subject and avoid returning all items. This example demonstrates a paginated query that respects ownership and uses a consistent pagination token.
import { DynamoDBClient, QueryCommand } from "@aws-sdk/client-dynamodb";
import { unmarshall } from "@aws-sdk/util-dynamodb";
const client = new DynamoDBClient({ region: "us-east-1" });
export default class TodosController {
async index({ auth, request }) {
const userId = auth.user.id;
const limit = request.q().limit || 20;
const exclusiveStartKey = request.q().startKey ? JSON.parse(request.q().startKey) : undefined;
const command = new QueryCommand({
TableName: process.env.DYNAMODB_TODOS_TABLE,
KeyConditionExpression: "userId = :uid",
ExpressionAttributeValues: {
":uid": { S: userId },
},
Limit: limit,
ExclusiveStartKey: exclusiveStartKey,
});
const response = await client.send(command);
return {
items: response.Items.map(unmarshall),
nextStartKey: response.LastEvaluatedKey ? response.LastEvaluatedKey : null,
};
}
}
By binding the query to userId as the partition key, you ensure that users can only traverse their own records. middleBrick’s BOLA/IDOR checks validate that such ownership filters are present at runtime, and its input validation verifies that pagination tokens are well-formed to prevent tampering.
3. Write operations with ownership verification
For create and update operations, verify that the item being modified belongs to the requester. In this create example, the item is written with the authenticated user as the partition key, preventing one user from submitting another user’s ID to hijack ownership.
import { DynamoDBClient, PutCommand } from "@aws-sdk/client-dynamodb";
import { marshall } from "@aws-sdk/util-dynamodb";
const client = new DynamoDBClient({ region: "us-east-1" });
export default class TodosController {
async store({ auth, request }) {
const userId = auth.user.id;
const { title, done = false } = request.only(["title", "done"]);
const command = new PutCommand({
TableName: process.env.DYNAMODB_TODOS_TABLE,
Item: marshall({
userId,
todoId: `todo-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
title,
done,
createdAt: new Date().toISOString(),
}),
});
await client.send(command);
return { success: true };
}
}
These patterns align with the principle of least privilege: IAM policies should grant DynamoDB permissions only for the specific keys and operations required. middleBrick’s Pro plan supports continuous monitoring and GitHub Action integration so that regressions in ownership checks are caught in CI/CD before deployment.
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 |