Broken Access Control in Sails with Dynamodb
Broken Access Control in Sails with Dynamodb — how this specific combination creates or exposes the vulnerability
Broken Access Control (BOLA/IDOR) in a Sails.js application backed by DynamoDB often stems from modeling decisions and authorization logic that do not enforce tenant or ownership boundaries at the data layer. Sails models that rely on high-level Waterline abstractions can inadvertently expose DynamoDB operations when developer-written queries do not scope items to the requesting user or tenant. For example, a find query that omits a partition key filter or relies only on client-supplied identifiers can allow an authenticated user to iterate across other users’ records by guessing valid IDs.
DynamoDB’s permission model compounds this risk if the service role attached to the application is overly permissive. Policies that include dynamodb:Scan or broad dynamodb:GetItem on a table without constraint-based conditionals enable horizontal privilege escalation: one compromised credential can yield access to many records. In addition, DynamoDB streams and Time to Live (TTL) can unintentionally surface sensitive data if downstream consumers or caches do not enforce access checks, creating an exposure path that is not visible at the API layer alone.
Consider a Sails controller that retrieves a user profile by ID without validating ownership:
module.exports = {
async profile(req, res) {
const id = req.param('id');
const profile = await User.findOne(id);
return res.ok(profile);
}
};
If the underlying User model maps to a DynamoDB table with a composite key where the partition key is PK (e.g., USER#<user_id>) and the developer does not enforce that req.user.id equals the record’s owning partition key, an attacker supplying any valid-looking ID can read other users’ data. This maps directly to OWASP API Top 10 A01:2023 Broken Access Control and can be flagged by middleBrick as a BOLA/IDOR finding with severity High.
Insecure API design can also expose DynamoDB via over-fetching endpoints that return entire items when only a subset is required. An endpoint that returns all attributes, including secrets or internal flags, increases data exposure impact. middleBrick’s Data Exposure and Property Authorization checks highlight such findings, linking them to compliance mappings in frameworks like PCI-DSS and SOC2.
Dynamodb-Specific Remediation in Sails — concrete code fixes
Remediation focuses on ensuring every DynamoDB operation is constrained by the requester’s identity or tenant context and that least privilege permissions are enforced. In Sails, this means explicitly building the key condition rather than relying on implicit Waterline behavior, and validating ownership before issuing any database call.
Use the AWS SDK directly to enforce partition and sort key scoping. For example, to fetch a user profile safely:
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
module.exports = {
async profile(req, res) {
const userId = req.user.id; // ensure req.user is populated by auth middleware
const params = {
TableName: 'myapp_users',
Key: {
PK: `USER#${userId}`,
SK: `PROFILE`
}
};
try {
const data = await dynamodb.get(params).promise();
if (!data.Item) return res.notFound();
return res.ok(data.Item);
} catch (err) {
sails.log.error('DynamoDB error', err);
return res.serverError();
}
}
};
This pattern guarantees that users can only retrieve items where the partition key matches their own identity, mitigating BOLA/IDOR. The same principle applies to list endpoints: use a composite key design where the partition key encodes the tenant or user, and the sort key supports queries that remain scoped.
For broader queries, enforce filters at the application layer after retrieving a limited, authenticated set. Avoid scans; prefer queries with KeyConditionExpression. If you must scan, ensure the IAM role used by the Sails app does not permit dynamodb:Scan in production and apply condition expressions to restrict scope.
Apply attribute-level authorization before returning data. For instance, strip sensitive fields based on roles:
function sanitizeProfile(item, requestingUser) {
if (item.userId !== requestingUser.id) {
throw new Error('Unauthorized');
}
const { passwordHash, internalFlags, ...publicData } = item;
return publicData;
}
module.exports = {
async me(req, res) {
const params = {
TableName: 'myapp_users',
Key: {
PK: `USER#${req.user.id}`,
SK: `PROFILE`
}
};
const data = await dynamodb.get(params).promise();
return res.ok(sanitizeProfile(data.Item, req.user));
}
};
middleBrick’s findings can guide you to specific endpoints and DynamoDB access patterns that require tightening. Combine these code-level controls with the Pro plan’s continuous monitoring and CI/CD integration to fail builds if risk scores degrade, ensuring that authorization checks remain enforced as code evolves.