Insecure Design in Feathersjs with Hmac Signatures
Insecure Design in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Insecure design in a FeathersJS service that uses HMAC signatures often arises when the HMAC is applied only to the request body or only to selected endpoints, while other endpoints remain unauthenticated or accept unsigned requests. A typical anti-pattern is to add an HMAC header check via a hook without enforcing it consistently across all service methods, routes, and transports. For example, a hook that reads req.headers['x-api-signature'] and verifies it against a shared secret may be missing from some methods, or may be bypassed if the client can choose which transport (HTTP, Socket.io) to use, because the hook is not applied to all transports. FeathersJS allows services to be configured with different transports, and if the HMAC validation is implemented only for HTTP transport and omitted for Socket.io, an attacker can interact with the unprotected transport and bypass the signature check entirely.
Another insecure design pattern is to embed the HMAC secret in client-side code or to distribute it statically across many clients. If the secret is static and shared widely, compromise of any single client exposes the secret for all clients, enabling an attacker to forge valid HMACs for arbitrary requests. Additionally, an insecure design may accept the signature in an easily leaked location (e.g., browser logs, Referer headers) or use a weak algorithm such as MD5 or SHA1 without key stretching, making brute-force or collision attacks feasible. Poor nonce or timestamp handling also leads to replay vulnerabilities: without strict one-time use tracking or reasonable time windows, an intercepted signed request can be resent to the API to perform unauthorized actions such as creating records or escalating privileges.
In the context of OWASP API Top 10, these design choices directly relate to Broken Object Level Authorization (BOLA/IDOR) and Broken Function Level Authorization (BFLA), because missing or inconsistent HMAC enforcement can allow attackers to act on behalf of other users or invoke administrative functions. For example, an endpoint like /users/:id may be intended to allow users to view only their own data, but if the HMAC is not validated for certain query parameters or if the signature does not bind to the user ID, an attacker can iterate over user IDs and access others’ data. Similarly, an admin endpoint such as /roles/:roleId/assign might be protected by HMAC in some calls but not others, enabling privilege escalation if the attacker discovers the unprotected path.
An insecure design may also fail to validate the scope of the signed payload, allowing parameter manipulation outside the signed portion. If the HMAC covers only the JSON body but query strings or headers that influence behavior are unsigned, an attacker can modify those unsigned elements to change the operation outcome. For instance, changing a isAdmin flag in an unsigned query parameter or header while the HMAC-signed body claims a regular user can lead to unauthorized privilege escalation. Consistent coverage of all relevant message components, binding the signature to identifiers like user ID and timestamp, is required to prevent these bypasses.
To detect such issues, middleBrick scans API endpoints and identifies missing or inconsistent authentication mechanisms across transports and methods, highlighting insecure design patterns where HMAC is not uniformly enforced. The tool correlates findings with the API specification and runtime behavior to highlight BOLA/IDOR and BFLA risks tied to weak HMAC usage. middleBrick also flags weak algorithms, missing replay protections, and lack of binding between signature context and request parameters, providing prioritized findings with remediation guidance and mapping to compliance frameworks such as OWASP API Top 10 and SOC2 controls.
Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes
Remediation centers on enforcing HMAC verification consistently across all service methods and transports, using strong algorithms, and binding the signature to key request attributes. In FeathersJS, this is best implemented as a hook that runs before service methods and validates the HMAC for applicable requests. Below is an example of a robust HMAC verification hook for HTTP transport using SHA256 with a per-client secret stored securely server-side.
const crypto = require('node:crypto');
function verifyHmac(secret) {
return (context) => {
const { headers, method, url, body } = context.params;
const signature = headers['x-api-signature'];
if (!signature) {
throw new Error('Missing signature');
}
const payload = typeof body === 'string' ? body : JSON.stringify(body);
const expected = crypto
.createHmac('sha256', secret)
.update(method + '|' + url + '|' + payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
throw new Error('Invalid signature');
}
return context;
};
}
// Apply to all services and methods
app.service('todos').hooks({
before: {
all: [verifyHmac(process.env.HMAC_SECRET)]
}
});
To cover Socket.io transports, apply the same verification inside a custom channel or message hook, ensuring the signature is included in the message metadata and validated before processing. Use a per-client secret mapping keyed by a client identifier included in an unsigned header (e.g., x-api-key), and retrieve the secret from a secure store rather than embedding it in code. Below is a simplified example validating the signature within a Socket.io message handler while including replay protection via a timestamp and nonce.
const crypto = require('node:crypto');
const seenNonces = new Set();
function isValidTimestamp(ts, windowSeconds = 300) {
const diff = Date.now() - ts;
return Math.abs(diff) <= windowSeconds * 1000;
}
io.on('connection', (socket) => {
socket.on('secure-event', (msg) => {
const { clientKey, timestamp, nonce, data, signature } = msg;
if (!isValidTimestamp(timestamp)) {
socket.disconnect();
return;
}
if (seenNonces.has(nonce)) {
socket.disconnect();
return;
}
const secret = getClientSecretByKey(clientKey);
const payload = JSON.stringify(data);
const expected = crypto
.createHmac('sha256', secret)
.update('emit|' + payload + '|' + timestamp + '|' + nonce)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
socket.disconnect();
return;
}
seenNonces.add(nonce);
// process event
});
});
To prevent replay and binding issues, include a timestamp and a nonce in the signed payload and enforce uniqueness of nonces within the timestamp window. For query parameters that influence behavior, incorporate them into the signed string to ensure any modification invalidates the signature. Below is an example that signs method, path, query string, and body, which prevents tampering with unsigned parameters that affect authorization or resource access.
const crypto = require('node:crypto');
function verifyHmacFull(secret) {
return (context) => {
const { method, url, headers, query, body } = context.params;
const signature = headers['x-api-signature'];
if (!signature) {
throw new Error('Missing signature');
}
const parsed = new URL(url, 'http://localhost');
const qs = parsed.search;
const payload = typeof body === 'string' ? body : JSON.stringify(body);
const dataToSign = [method, parsed.pathname, qs, payload].join('|');
const expected = crypto
.createHmac('sha256', secret)
.update(dataToSign)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
throw new Error('Invalid signature');
}
return context;
};
}
app.service('records').hooks({
before: {
all: [verifyHmacFull(process.env.HMAC_SECRET)]
}
});
For production, rotate secrets periodically, store them in a secure vault, and avoid static sharing across many clients. Combine HMAC with rate limiting and audit logging to detect and respond to abuse. middleBrick can validate that HMAC checks are present across all methods and transports and that strong algorithms and binding practices are used, surfacing insecure design issues with remediation steps and mapping to relevant compliance requirements.