Formula Injection in Feathersjs with Basic Auth
Formula Injection in Feathersjs with Basic Auth — how this specific combination creates or exposes the vulnerability
Formula Injection occurs when untrusted input is interpreted as a formula or expression by a backend service such as a spreadsheet, database query builder, or calculation engine. In Feathersjs applications that use Basic Auth for access control, risk arises when an endpoint accepts user-controlled values that later influence dynamic expressions evaluated server-side.
Feathersjs is a framework for building REST and real-time APIs. When authentication is implemented via a custom Basic Auth hook, the service may extract credentials from the Authorization header, validate them, and then proceed to process query parameters or body fields. If these fields are used to construct dynamic queries or expressions—such as building filter objects that are passed to an ORM or a spreadsheet-style calculation service—untrusted input can alter the logic of the expression.
Consider a scenario where a Feathersjs service computes a financial result using user-supplied data combined with internal values. An attacker may supply formula fragments like "1;=1+1" or structured payloads such as {"revenue":"1000+1"} in request fields. If the service concatenates these strings into an evaluative context (for example, constructing an Objection.js query or a custom calculation routine), the attacker may change the intended calculation or bypass intended filters. This becomes more dangerous when combined with Basic Auth, because the authentication step may appear to provide sufficient access control while the downstream logic trusts data that should have been validated and sanitized.
The vulnerability is particularly concerning when the compromised data influences access control decisions or data visibility. For instance, if a formula-derived value is used to build a where clause without strict type checks, an attacker might inject logical tautologies such as {"id":{"$ne":null}} to retrieve unauthorized records. Because Feathersjs services often expose multiple transport layers (REST and Socket.io), the same injected formula could be delivered through different channels, increasing the attack surface.
In a black-box scan, middleBrick tests for Formula Injection by submitting expressions in input fields and observing whether the response behavior changes in unintended ways. When Basic Auth is present, the scanner first authenticates using provided credentials to establish a valid session, then probes endpoints that accept dynamic data. The goal is to detect whether user-controlled inputs are reflected in calculations, queries, or responses in a way that modifies logic or data visibility.
Because Feathersjs encourages a modular architecture—hooks, services, and transports—developers must ensure that each layer validates and sanitizes inputs independently. Relying solely on Basic Auth for security without validating downstream data usage leaves room for logic manipulation, data leakage, or privilege escalation through crafted expressions.
Basic Auth-Specific Remediation in Feathersjs — concrete code fixes
Securing a Feathersjs service that uses Basic Auth requires strict input validation, type enforcement, and separation of authentication from data processing. The following examples demonstrate how to implement secure patterns.
First, ensure your authentication hook validates credentials without exposing sensitive information. The following hook verifies a username and password against a hashed store and attaches a safe user object to the context:
// src/hooks/basic-auth.js
const bcrypt = require('bcryptjs');
module.exports = function () {
return async context => {
const { authentication } = context;
if (!authentication || !authentication.strategy === 'basic') {
throw new Error('Not authenticated');
}
const { username, password } = authentication.payload;
if (!username || !password) {
throw new Error('Missing credentials');
}
const user = await context.app.service('users').Model.findOne({
where: { username }
});
if (!user) {
throw new Error('Invalid credentials');
}
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) {
throw new Error('Invalid credentials');
}
// Attach a minimal, trusted user representation
context.params.user = {
id: user.id,
role: user.role
};
delete context.params.authentication;
return context;
};
};
Next, configure your services to treat incoming data as untrusted. Use validation libraries to enforce types and reject formula-like strings in numeric fields:
// src/services/calculation/hooks.js
const { iff, isProvider } = require('feathers-hooks-common');
const { sanitizeQuery } = require('feathers-commons');
const validateNumeric = value => {
if (typeof value === 'string' && /[+\-*/;=()]/.test(value)) {
throw new Error('Invalid characters in numeric input');
}
const num = Number(value);
if (!Number.isFinite(num)) {
throw new Error('Invalid number');
}
return num;
};
module.exports = {
before: {
create: [context => {
const data = context.data || {};
if (typeof data.revenue === 'string') {
context.data.revenue = validateNumeric(data.revenue);
}
if (typeof data.cost === 'string') {
context.data.cost = validateNumeric(data.cost);
}
return context;
}],
update: [iff(isProvider('external'), sanitizeQuery)],
patch: [iff(isProvider('external'), sanitizeQuery)]
}
};
Finally, avoid constructing dynamic expressions by concatenating user input. Instead, use parameterized queries or explicit mapping. For example, if you must build a filter object, map allowed fields explicitly:
// src/services/reports/hooks.js
const allowedFields = new Set(['revenue', 'cost', 'profit', 'period', 'region']);
module.exports = {
before: {
create: context => {
const filter = context.data.filter || {};
const safeFilter = {};
for (const key of Object.keys(filter)) {
if (allowedFields.has(key) && typeof filter[key] === 'string') {
// Reject formula-like inputs
if (/[+\-*/;=()]/.test(filter[key])) {
throw new Error(`Invalid filter value for ${key}`);
}
safeFilter[key] = filter[key];
}
}
context.data.filter = safeFilter;
return context;
}
}
};
These patterns ensure that authentication does not create a false sense of security and that data processing remains deterministic and safe from injected logic.