HIGH webhook abusefeathersjshmac signatures

Webhook Abuse in Feathersjs with Hmac Signatures

Webhook Abuse in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability

FeathersJS is a framework for real-time web applications that commonly uses webhooks to integrate with external services. A webhook is an HTTP callback that notifies a consumer when an event occurs. When HMAC signatures are used to authenticate webhook messages, the intent is to verify that the payload originates from a trusted sender by checking a cryptographic signature.

Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes

To securely verify HMAC signatures in FeathersJS, compare the signature in a header with a computed HMAC of the payload using a constant-time comparison to prevent timing attacks. Use a strong secret, and ensure the secret is not exposed in logs or error messages. Below are concrete, working examples for Express-style FeathersJS services.

Example: Securing a FeathersJS webhook endpoint with HMAC verification

const crypto = require('crypto');
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');

const app = express(feathers());

// Shared secret stored securely (e.g., environment variable)
const HMAC_SECRET = process.env.WEBHOOK_HMAC_SECRET;
if (!HMAC_SECRET) {
  throw new Error('WEBHOOK_HMAC_SECRET must be set');
}

// Middleware to verify HMAC signature
function verifyHmac(req, res, next) {
  const signature = req.get('x-hub-signature-256');
  if (!signature) {
    return res.status(401).send('Missing signature');
  }

  const payload = JSON.stringify(req.body);
  const hmac = crypto.createHmac('sha256', HMAC_SECRET);
  const digest = 'sha256=' + hmac.update(payload).digest('hex');

  // Use timing-safe comparison
  const isValid = crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }
  next();
}

// Example webhook service
app.use('/webhook', {
  async create(data, params) {
    // The signature was verified by middleware
    // data is the verified webhook payload
    console.log('Received webhook:', data);
    return { received: true };
  },
  async update(id, data, params) {},
  async patch(id, data, params) {},
  async remove(id, params) {}
}, verifyHmac);

// Parse JSON bodies before HMAC verification
app.use(express.json());

// Start server (for local testing only)
app.listen(3030).then(server => {
  console.log('Feathers webhook listener on 3030');
});

Example: Sending a signed webhook from a producer to a FeathersJS consumer

const crypto = require('crypto');

function sendSignedWebhook(url, payload, secret) {
  const body = JSON.stringify(payload);
  const hmac = crypto.createHmac('sha256', secret);
  const signature = 'sha256=' + hmac.update(body).digest('hex');

  return fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Hub-Signature-256': signature
    },
    body: body
  }).then(res => {
    if (res.status !== 200) {
      console.error('Webhook failed:', res.status);
    }
    return res;
  });
}

// Usage
const secret = process.env.WEBHOOK_HMAC_SECRET;
sendSignedWebhook('http://localhost:3030/webhook', { event: 'order.created', orderId: 123 }, secret)
  .then(() => console.log('Sent signed webhook'))
  .catch(err => console.error('Error:', err));

Frequently Asked Questions

What should I do if a webhook signature verification fails in FeathersJS?
Return a 401 status and log the failure without processing the payload. Do not attempt to parse or act on the body, and avoid exposing whether the failure was due to a malformed signature or missing secret in error details returned to the sender.
How should I store and rotate the HMAC secret for FeathersJS webhooks?
Store the secret in a secure environment variable or a secrets manager. Rotate it periodically and support a short overlap window where endpoints accept either the old or new secret to avoid breaking existing integrations during rotation.