Api Rate Abuse in Feathersjs with Hmac Signatures
Api Rate Abuse in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Rate abuse in FeathersJS applications that use HMAC signatures can occur when signature verification does not sufficiently constrain request uniqueness. HMAC signatures are typically computed over a combination of payload fields, timestamps, and nonces. If the rate-limiting logic operates only after signature validation and does not account for signature variability, an attacker can generate many distinct signed requests that each pass authentication but collectively exhaust server-side rate limits.
In FeathersJS, services often rely on hooks for authentication. A common pattern is to verify an HMAC signature in a before hook, then allow the request to proceed. Consider a service where the client sends data, a timestamp, and a signature derived with a shared secret. If the server validates the signature and then passes the request to the Feathers service without tying the signature to a per-client rate limit, an attacker can rotate through valid signatures by slightly altering payload content or timestamps, bypassing naive request counting that does not account for authenticated identity.
For example, an attacker can send requests with incrementing timestamps or unique nonces while keeping the HMAC valid, creating many distinct signed requests. If FeathersJS does not enforce rate limits at the client identity level (e.g., by API key or derived client ID from the HMAC), the server may only enforce global request thresholds. This exposes the unauthenticated attack surface to enumeration or resource exhaustion, which middleBrick scans as part of its Rate Limiting and Authentication checks.
Another scenario involves signature replay within the validity window. If a signed request is captured and replayed multiple times before server-side rate limiting detects excessive usage, the attacker can abuse the endpoint without triggering defenses tied to request volume. Because HMACs do not inherently bind requests to a strict one-use nonce unless explicitly enforced, replay can occur when FeathersJS services do not track processed nonces or do not couple rate limits with signature metadata.
Because FeathersJS is often used with real-time transports and REST endpoints, the combination of HMAC-based authentication and insufficient per-client rate controls can amplify the impact of abuse. The server may handle many authenticated-but-malicious requests within the 5–15 second scan window that middleBrick uses, revealing findings related to weak binding between authentication signatures and rate-limiting policies.
Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes
To mitigate rate abuse when using HMAC signatures in FeathersJS, bind rate limits to the entity derived from the HMAC, such as the client identifier encoded in the payload or computed from the signing key. Ensure that each signed request includes a nonce or timestamp tightly coupled with server-side tracking to prevent replay and enforce per-client quotas.
Below is a concrete FeathersJS hook that verifies an HMAC signature and enforces a per-client rate limit using an in-memory store; in production, replace the store with Redis or another shared backend to work correctly across multiple instances.
const crypto = require('crypto');
const clientNonceStore = new Map();
function verifyHmac(payload, receivedSignature, secret) {
const { timestamp, nonce, data } = payload;
const baseString = `${timestamp}:${nonce}:${JSON.stringify(data)}`;
const expectedSignature = crypto.createHmac('sha256', secret).update(baseString).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(receivedSignature));
}
function rateLimitByClient(clientId, limit, windowMs) {
const now = Date.now();
if (!clientNonceStore.has(clientId)) {
clientNonceStore.set(clientId, []);
}
const timestamps = clientNonceStore.get(clientId).filter(ts => now - ts < windowMs);
if (timestamps.length >= limit) {
return false;
}
timestamps.push(now);
clientNonceStore.set(clientId, timestamps);
return true;
}
module.exports = function hmacRateLimitHook(options = {}) {
const { secret, rateLimit = 30, rateWindow = 60000 } = options;
return async context => {
const { data } = context.params;
if (!data || typeof data !== 'object') {
throw new Error('Invalid request payload');
}
const { timestamp, nonce, signature } = data;
if (typeof timestamp !== 'number' || typeof nonce !== 'string' || !signature) {
throw new Error('Missing required HMAC fields');
}
const clientId = data.clientId;
if (!clientId || typeof clientId !== 'string') {
throw new Error('clientId is required to identify the caller');
}
if (!verifyHmac(data, signature, secret)) {
throw new Error('Invalid HMAC signature');
}
if (!rateLimitByClient(clientId, rateLimit, rateWindow)) {
const err = new Error('Rate limit exceeded');
err.code = 429;
throw err;
}
// Optionally store processed nonce to prevent replay within window
const replayStoreKey = `${clientId}:${nonce}`;
if (context.app.get('processedNonces')?.has(replayStoreKey)) {
throw new Error('Replay detected');
}
context.app.set('processedNonces', new Set(context.app.get('processedNonces') || []));
context.app.get('processedNonces').add(replayStoreKey);
return context;
};
};
In this example, the HMAC is computed over timestamp, nonce, and the request data, and the signature is verified before any service logic runs. The rate limiter is keyed by clientId, which should be a stable identifier included in the signed payload. Nonces are tracked to mitigate replay within the rate-limit window. For production deployments, use a distributed store and align the window with your operational monitoring.
The GitHub Action can be configured to validate that such hooks exist and that rate-limiting thresholds are present in service configurations, while the CLI can output findings when scanning FeathersJS apps. middleBrick’s checks include Rate Limiting and Authentication, which will surface weaknesses in how HMAC signatures interact with request throttling.