HIGH phishing api keysfeathersjshmac signatures

Phishing Api Keys in Feathersjs with Hmac Signatures

Phishing Api Keys in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability

FeathersJS is a popular framework for building REST and Socket API services in Node.js. When HMAC signatures are used for request authentication, a common implementation pattern is to have the client sign a canonical representation of the request (method, path, timestamp, and payload) using a shared secret that corresponds to an API key. If the server-side validation logic is flawed, this setup can enable phishing-like attacks where an attacker obtains or reuses a valid API key and signature to impersonate a client.

For example, consider a FeathersJS service that authenticates requests by verifying an X-API-Key header and an X-Signature header. If the server only checks that the API key exists and the signature matches, but does not adequately bind the signature to the request context (including method, path, host, and an anti-replay timestamp), an attacker can phish or leak a valid key and signature pair (for instance, via logs, error messages, or a compromised client) and reuse it to make authorized requests to the same endpoint. This is a BOLA/IDOR-style issue where the identifier (the API key) and the proof of possession (the HMAC signature) are not sufficiently scoped to prevent replay or misuse across different contexts.

A concrete insecure FeathersJS authentication hook might look like this, highlighting the weaknesses:

// Insecure example: validates key and signature but does not bind to method/path/timestamp
const crypto = require('crypto');
app.hooks({
  before: {
    all: [async context => {
      const { apikey, signature } = context.params.headers;
      const secret = getSecretForKey(apikey); // assumes a lookup
      if (!secret) throw new Error('Unauthorized');
      const expected = crypto.createHmac('sha256', secret).update(context.method + context.path).digest('hex');
      if (!timingSafeEqual(expected, signature)) {
        throw new Error('Invalid signature');
      }
      // Missing: timestamp/nonce validation, host binding, replay protection
      return context;
    }]
  }
});

In this example, the signature is computed over only method and path. An attacker who obtains a valid apikey and corresponding signature can reuse them to call any endpoint on the same host, effectively phishing the API key’s privileges. The absence of a per-request nonce or timestamp enables replay, and missing host binding opens the possibility that a signature captured for one host is reused against another (host confusion). A robust implementation must ensure the signature covers the full request context and includes mechanisms to prevent replay.

The LLM/AI Security checks in middleBrick specifically test for System prompt leakage and prompt injection patterns; this scenario is unrelated to those AI-specific checks but aligns with the broader class of authentication bypass and insufficient binding findings that scanners report. middleBrick’s scan would flag missing replay protection and weak signature binding as actionable findings with remediation guidance.

Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes

To remediate phishing risks with HMAC signatures in FeathersJS, bind the signature to the full HTTP request context and include replay protection. The server should verify the timestamp (and optionally a nonce), include the host, and use a constant-time comparison. Below is a hardened example that includes these protections.

First, define a helper for constant-time comparison and timestamp tolerance:

const crypto = require('crypto');
const timingSafeEqual = (a, b) => {
  if (a.length !== b.length) return false;
  let result = 0;
  for (let i = 0; i < a.length; i++) {
    result |= a[i] ^ b[i];
  }
  return result === 0;
};
const ALLOWED_CLOCK_SKEW_SECONDS = 30;
const isTimestampValid = (ts) => {
  const now = Math.floor(Date.now() / 1000);
  return Math.abs(now - ts) <= ALLOWED_CLOCK_SKEW_SECONDS;
};

Then, implement the HMAC verification in a before hook that includes method, host, path, timestamp, and an optional nonce or body hash to prevent tampering:

app.hooks({
  before: {
    all: [async context => {
      const { apikey, signature, timestamp, nonce } = context.params.headers;
      if (!apikey || !signature || !timestamp) {
        throw new Error('Missing required authentication headers');
      }
      if (!isTimestampValid(Number(timestamp))) {
        throw new Error('Stale request: timestamp outside allowed skew');
      }
      const secret = getSecretForKey(apikey); // safe lookup returning null if not found
      if (!secret) {
        throw new Error('Unauthorized');
      }
      const payload = [
        context.method.toUpperCase(),
        context.host,
        context.path,
        timestamp,
        nonce || '',
        typeof context.data === 'string' ? context.data : JSON.stringify(context.data || {})
      ].join('|');
      const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
      if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
        throw new Error('Invalid signature');
      }
      // Optionally store the key identifier on context for downstream use
      context.params.accountId = apikey;
      return context;
    }]
  }
});

On the client, ensure you send the same components used in the server’s payload construction. Example using fetch:

const apikey = 'YOUR_API_KEY';
const secret = 'SHARED_SECRET'; // known only to client and server
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = cryptoRandomHex(16); // implement a secure random hex generator
const payload = [
  'GET',
  'api.example.com',
  '/users',
  timestamp,
  nonce,
  ''
].join('|');
const signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');
fetch('https://api.example.com/users', {
  method: 'GET',
  headers: {
    'X-API-Key': apikey,
    'X-Timestamp': timestamp,
    'X-Nonce': nonce,
    'X-Signature': signature
  }
});

Additional recommendations: rotate secrets periodically, avoid logging full signatures or secrets, and prefer short-lived credentials where feasible. middleBrick’s CLI can be used to validate these fixes by scanning your FeathersJS endpoints; the CLI outputs structured JSON findings that map missing replay protection and weak binding to OWASP API Top 10 and other compliance frameworks.

If you integrate middleBrick into CI/CD via the GitHub Action, you can fail builds automatically when risk scores degrade, ensuring authentication regressions are caught before deployment. The MCP Server enables scanning directly from AI coding assistants within your IDE, providing rapid feedback during development.

Frequently Asked Questions

What specific missing protections does middleBrick report for HMAC-based APIs in FeathersJS?
middleBrick reports findings such as missing timestamp/nonce validation, lack of host binding in signature computation, absence of replay protection, and weak signature scope (e.g., signing only method/path). These map to authentication bypass and insufficient binding categories.
Can middleBrick fix the phishing API key issue in FeathersJS automatically?
middleBrick detects and reports these issues with remediation guidance but does not fix, patch, block, or remediate. Developers must apply the code fixes, such as binding signature to full request context and adding replay protection.