Insecure Design in Loopback (Javascript)
Insecure Design in Loopback with Javascript — how this specific combination creates or exposes the vulnerability
Insecure design in a Loopback application written in JavaScript often stems from insufficient authorization checks on domain logic models and remote methods. Loopback models can expose CRUD-style endpoints and custom remote methods that, if designed without explicit per-request authorization, allow a subject to act on resources that belong to another subject (BOLA/IDOR). Because JavaScript is dynamically typed, it is easy to omit ownership checks or to trust client-supplied identifiers (e.g., req.params.id) without verifying that the authenticated subject has rights to that specific instance.
For example, a model named Project might define a remote method findById that directly uses the provided id to fetch a record. If the method does not scope the query by the authenticated user’s tenant or user ID, an attacker can iterate through numeric IDs or UUIDs and access projects they should not see. This becomes an insecure design when the API surface assumes that clients will not tamper with identifiers, rather than enforcing authorization at the model or method boundary.
JavaScript-specific patterns in Loopback can amplify this: using asynchronous callbacks or Promises without ensuring that authorization logic runs before data access, or building query objects from user input without strict validation, can lead to unchecked or over-permissive filters. An attacker might supply a JSON payload that injects additional filter fields (e.g., {">": {"id": 0}}) to bypass intended filters if input validation is weak. The combination of Loopback’s model-binding behavior and JavaScript’s flexibility makes it straightforward to inadvertently expose broad read or write access when authorization is not explicitly encoded in every handler.
Compliant with relevant checks in middleBrick’s 12 security checks, such flaws map to BOLA/IDOR and Property Authorization, and are surfaced with severity and remediation guidance. Because middleBrick scans unauthenticated attack surfaces and supports OpenAPI/Swagger spec analysis (with full $ref resolution), it can detect endpoints where design decisions fail to enforce scoping, even when the API spec appears correct at a glance.
To illustrate, consider a Loopback model definition and a remote method that lacks ownership validation. middleBrick would flag the absence of per-request checks and provide prioritized findings with remediation steps, enabling developers to adjust the design before deployment.
Javascript-Specific Remediation in Loopback — concrete code fixes
Remediation focuses on enforcing ownership and scoping at the model or remote method level, validating and sanitizing all inputs, and avoiding implicit trust in client-supplied identifiers. Below are concrete JavaScript examples for Loopback that demonstrate secure patterns.
1. Scope queries by authenticated subject
Ensure every data access includes the subject’s identifier. If using an authentication middleware that attaches req.user, use it to filter results.
// models/project.js
module.exports = function(Project) {
Project.findByUser = function(userId, cb) {
Project.find({
where: { userId: userId },
order: 'created DESC'
}, cb);
};
Project.remoteMethod('findByUser', {
http: { path: '/for-user', verb: 'get' },
accepts: { arg: 'userId', type: 'string', required: true },
returns: { arg: 'projects', type: ['Project'] }
});
};
In your controller or route handler, pass req.user.id (or equivalent) rather than accepting an arbitrary ID from the client, unless you explicitly verify that the requesting user owns the supplied ID.
2. Validate and sanitize inputs in remote methods
Do not trust query parameters or body values. Normalize and validate before constructing queries.
// common/controllers/project.controller.js
module.exports = function(app) {
const Project = app.models.Project;
Project.safeList = function(filters, options, cb) {
// Validate filters shape and types
if (!filters || typeof filters !== 'object') {
return cb(new Error('Invalid filters'));
}
const where = Object.assign({}, filters.where || {});
// Ensure no injection of additional operators
if (where.id && typeof where.id !== 'string' && typeof where.id !== 'number') {
return cb(new Error('Invalid id type'));
}
// Scoping: require tenantId to be provided by the server, never from client
where.tenantId = options.ctx && options.ctx.tenantId ? options.ctx.tenantId : null;
if (!where.tenantId) {
return cb(new Error('Access denied'));
}
Project.find({ where }, cb);
};
};
This pattern avoids over-permissive filtering and ensures server-supplied scoping criteria (like tenant ID) cannot be overridden by the client.
3. Use strict model ACLs and remote method ACLs
Define access control lists that reflect least privilege and explicitly deny public write access unless intended.
// models/project.json
{
"name": "Project",
"base": "PersistedModel",
"acls": [
{ "principalType": "ROLE", "principalId": "$everyone", "permission": "DENY", "property": "deleteById" },
{ "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW", "property": "find" }
],
"methods": {
"adminOnlyAction": {
"http": { "path": "/admin-action", "verb": "post" },
"scope": "admin",
"acls": [
{ "principalType": "ROLE", "principalId": "$authenticated", "permission": "ALLOW" }
]
}
}
}
Combine ACLs with runtime checks inside remote method implementations for defense in depth.
4. Avoid exposing internal identifiers in URLs
Prefer opaque identifiers or map public slugs to internal IDs server-side, and verify ownership on each request.
// models/project.js — safe lookup by slug, not by raw numeric ID
Project.findBySafeRef = function(ref, options, cb) {
if (!ref || typeof ref !== 'string') return cb(new Error('Invalid ref'));
options.ctx.tenantId; // ensure server-supplied scope
Project.findOne({ where: { slug: ref, tenantId: options.ctx.tenantId } }, cb);
};