Api Key Exposure in Feathersjs with Hmac Signatures
Api Key Exposure in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework for creating JavaScript APIs with minimal configuration. When HMAC signatures are used for request authentication, developers often implement a shared secret that signs a canonical representation of the request (method, path, timestamp, and payload). If the implementation leaks the raw API key or the shared secret through logs, error messages, or client‑side code, an attacker who gains that material can forge valid signatures and impersonate any service account.
In FeathersJS, a common pattern is to compute an HMAC over selected request properties and include the signature in a custom header (e.g., x-api-key and x-signature). A vulnerability arises when the server echoes the received API key in responses or errors, or when the same key is used both for client identification and for signing without adequate separation. For example, if the client sends an API key in a header and the server includes that key verbatim in stack traces or debug output, an authenticated or unauthenticated attacker who can trigger error conditions may observe the key through SSRF or log exposure. This becomes especially risky when the signing function is implemented in a way that does not properly validate the presence and integrity of the key before processing, allowing an attacker to probe whether a given key results in a valid signature via error differences (side‑channel or injection indicators).
Another exposure path is through the OpenAPI/Swagger spec. If the spec documents the API key header but does not clearly indicate that it must be treated as sensitive, or if the spec is shared publicly (for example in a generated client), the key name and location become known to anyone with access to the documentation. Even when runtime validation is strict, metadata leakage in the spec can aid reconnaissance. Moreover, if the HMAC implementation uses a weak timestamp window or does not enforce one‑time use of nonces, replay attacks may be feasible, indirectly exposing key usage patterns that can be correlated with other findings such as BOLA/IDOR.
Consider a FeathersJS service defined with hooks that compute HMAC over params.query and params.headers. If the hook accidentally passes the raw secret to a logging utility or does not strip the secret from objects before serialization, the key can be exposed in application logs. An attacker who can read logs (for instance via a compromised CI/CD artifact or a verbose error page in development) can extract the secret and generate valid signed requests. This is a classic case where improper handling of the API key within the HMAC workflow turns a supposed integrity mechanism into a disclosure channel.
middleBrick detects such risks by analyzing the unauthenticated attack surface, including OpenAPI/Swagger specs with full $ref resolution, and by correlating runtime behavior with security checks. It flags conditions where API keys appear in error responses, are overly verbose in documentation, or are used in ways that could enable leakage through logs or SSRF. The scanner does not modify the application; it highlights where exposure is likely and provides remediation guidance to reduce the chance of accidental disclosure.
Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes
To mitigate API key exposure when using HMAC signatures in FeathersJS, adopt strict separation between identification and signing, avoid leaking secrets, and ensure the signing process is robust. Below are concrete, working examples that demonstrate secure implementations.
Secure HMAC signing server hook
Use a server-side hook to validate the signature without exposing the secret. Compute the expected signature over a canonical string and compare it using a constant‑time comparison to avoid timing attacks. Never return the secret in any response or log entry.
// server/hooks/hmac-validate.js
const crypto = require('node:crypto');
const constantTimeCompare = require('safe-compare');
module.exports = function hmacValidate(options = {}) {
return async context => {
const { secret } = options;
if (!secret) {
throw new Error('HMAC secret is not configured');
}
const { headers, path, query } = context.params;
const receivedSignature = headers['x-signature'];
const apiKey = headers['x-api-key'];
if (!receivedSignature || !apiKey) {
throw new Error('Missing authentication headers');
}
// Canonical representation: method is GET for query-only safe operations
const payload = JSON.stringify({ path, query });
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const expectedSignature = hmac.digest('hex');
if (!constantTimeCompare(expectedSignature, receivedSignature)) {
// Generic error to avoid leaking why validation failed
throw new Error('Invalid signature');
}
// Optionally attach identity derived from key mapping, not the key itself
context.params.accountId = mapApiKeyToAccount(apiKey);
return context;
};
};
Client example computing the signature
The client should build the same canonical representation and sign with the shared secret, sending only the derived signature and the public API key identifier.
// client/request.js
const crypto = require('node:crypto');
function signRequest({ url, method, query, secret }) {
const path = new URL(url).pathname;
const payload = JSON.stringify({ path, query });
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
return hmac.digest('hex');
}
const apiKey = process.env.API_KEY_IDENTIFIER; // public identifier, not secret
const secret = process.env.HMAC_SECRET; // stored securely, never logged
const signature = signRequest({ url: 'https://api.example.com/users', method: 'GET', query: { limit: '10' }, secret });
fetch('https://api.example.com/users', {
method: 'GET',
headers: {
'x-api-key': apiKey,
'x-signature': signature,
'Content-Type': 'application/json'
}
});
FeathersJS service configuration and hook registration
Register the validation hook before the service handler to ensure every incoming request is verified. Avoid including sensitive fields in service methods that might be serialized to clients.
// src/services/secure/secure.hooks.js
const hmacValidate = require('../../hooks/hmac-validate');
module.exports = {
before: {
all: [hmacValidate({ secret: process.env.HMAC_SIGNING_SECRET })],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
after: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
error: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
};
Operational best practices
- Never log the shared secret or the full signed payload in production.
- Map API keys to accounts server-side; do not expose mapping logic to the client.
- Use short timestamp windows and nonces where appropriate to limit replay risk.
- Keep the OpenAPI spec accurate but avoid documenting the raw secret; mark sensitive headers appropriately in the spec so tooling does not expose them inadvertently.
By applying these patterns, you reduce the risk that the API key or signing material is inadvertently exposed through errors, logs, or documentation. The hooks ensure validation happens close to the service layer, and the client example shows how to generate signatures without embedding secrets in browser code.