HIGH webhook abusefeathersjsapi keys

Webhook Abuse in Feathersjs with Api Keys

Webhook Abuse in Feathersjs with Api Keys — how this specific combination creates or exposes the vulnerability

FeathersJS is a framework for real-time APIs that commonly uses service hooks and transports such as REST and Socket.io. Webhooks in this context are typically implemented as external HTTP callbacks registered by clients or administrators to receive events (e.g., order.created, user.signup). When API keys are used for authorization without additional constraints, they can inadvertently enable webhook abuse.

An API key is a bearer credential; if it is static, long-lived, and scoped broadly, it can be used not only to call internal services but also to register, update, or delete webhook endpoints. An attacker who compromises or guesses an API key with write permissions can register a malicious webhook pointing to a server they control. Because the webhook will be invoked by the FeathersJS service on real events, the attacker can reliably receive sensitive data, trigger downstream actions, or exfiltrate information through webhook deliveries.

In FeathersJS, webhooks are often implemented via hooks that publish events to external URLs using HTTP POST. If these hooks rely solely on API key validation and do not enforce origin restrictions, scope limitations, or idempotency controls, the system becomes vulnerable to several abuse patterns. For example, an attacker can use a valid API key to create a webhook like https://attacker.example.com/collect that listens to user.patches or order.updates. Each event triggers an HTTP call from FeathersJS to the attacker endpoint, potentially delivering authentication tokens, PII, or business logic details in the payload.

Additionally, because API keys are often embedded in client-side code or configuration files, they can be leaked through logs, version control, or error messages. Once leaked, the key can be used to register webhooks at scale, leading to high-volume spam, denial-of-through via oversized webhook payloads, or credential harvesting via falsified callback URLs. Unlike session-based cookies, API keys rarely rotate automatically, increasing the window of exposure for webhook registrations made with stolen keys.

The risk is compounded when FeathersJS services are exposed publicly and API keys are the primary gatekeeper without supplementary signals such as IP allowlists, mutual TLS, or short-lived tokens. Real-world attack patterns like insecure direct object references (IDOR) can intersect with weak webhook authorization, allowing an attacker to modify or delete existing webhooks belonging to other users if the key has broad permissions.

Because middleBrick scans the unauthenticated attack surface and tests API keys alongside webhook configurations, it can surface these classes of risk. Note that middleBrick detects and reports such issues and provides remediation guidance, but it does not modify configurations or block traffic.

Api Keys-Specific Remediation in Feathersjs — concrete code fixes

Remediation focuses on reducing the authority of API keys, adding webhook-specific validation, and ensuring that keys cannot be abused to register arbitrary endpoints. Below are concrete, syntactically correct examples for FeathersJS that address webhook registration and API key usage.

1. Scope-limited API keys for webhook operations

Do not use the same key for webhook registration that is used for general data access. Introduce a dedicated scope or capability for webhook management, and enforce checks in the service hook.

// src/hooks/webhook-permission-hook.js
module.exports = function webhookPermissionHook() {
  return async context => {
    const { params } = context;
    // API key capabilities are stored in params.apiKeyScopes (example custom property)
    const scopes = params.apiKeyScopes || [];
    if (!scopes.includes('webhook:manage')) {
      throw new Error('Unauthorized: insufficient scope for webhook operation');
    }
    return context;
  };
};

2. Validate webhook target URLs and restrict domains

Ensure that registered webhook URLs match an allowlist of domains and reject open redirects or internal addresses.

// src/hooks/validate-webhook-url.js
const allowedDomains = ['https://hooks.example.com', 'https://collector.partner.example'];

module.exports = function validateWebhookUrlHook() {
  return async context => {
    const url = context.data?.url;
    if (!url) return context;
    try {
      const parsed = new URL(url);
      const isAllowed = allowedDomains.some(allowed => parsed.origin === allowed);
      if (!isAllowed) {
        throw new Error('Webhook URL not allowed');
      }
    } catch (error) {
      throw new Error('Invalid webhook URL');
    }
    return context;
  };
};

3. Use short-lived tokens for webhook delivery verification

Instead of relying on static API keys for webhook callbacks, issue short-lived tokens and verify them on receipt. This prevents replay and ensures that only your service can deliver events.

// src/hooks/sign-webhook-payload.js
const jwt = require('jsonwebtoken');

module.exports = function signWebhookPayloadHook() {
  return async context => {
    const secret = process.env.WEBHOOK_SIGNING_SECRET;
    const token = jwt.sign({ sub: context.data.id, iat: Math.floor(Date.now() / 1000) }, secret, { expiresIn: '5m' });
    context.data.auth_token = token;
    return context;
  };
};

// In your consumer endpoint (pseudo-code)
app.post('/webhook/collect', (req, res) => {
  const token = req.headers['x-auth-token'];
  try {
    const decoded = jwt.verify(token, process.env.WEBHOOK_SIGNING_SECRET);
    // process event
    res.status(200).send('ok');
  } catch (err) {
    res.status(401).send('invalid token');
  }
});

4. Rate limiting and idempotency for webhook registrations

Limit how often a given API key can create or update webhooks and ensure idempotent operations to reduce spam and accidental duplicates.

// src/hooks/rate-limit-webhook-registrations.js
const registrationCount = new Map();

module.exports = function rateLimitWebhookHook() {
  return async context => {
    const key = context.params.apiKeyId; // static key identifier
    const now = Date.now();
    const window = 60_000; // 1 minute
    const max = 5;
    const record = registrationCount.get(key) || { count: 0, start: now };
    if (now - record.start > window) {
      record.count = 1;
      record.start = now;
    } else {
      record.count += 1;
    }
    if (record.count > max) {
      throw new Error('Too many webhook registration requests');
    }
    registrationCount.set(key, record);
    return context;
  };
};

5. Secure storage and rotation of API keys

Store API keys in a secure vault and rotate them periodically. In FeathersJS, inject keys via environment variables and avoid embedding them in source code.

// src/app.js
const feathers = require('@feathersjs/feathers');
const rest = require('@feathersjs/rest');
const app = feathers();

// Example: API key read from environment, not hardcoded
app.configure(rest({
  apiKeysSource: process.env.API_KEY_SOURCE || 'vault'
}));

Frequently Asked Questions

Can middleBrick detect webhook configurations that rely solely on static API keys?
Yes. middleBrick scans the unauthenticated attack surface and tests API key usage patterns, including webhook registration endpoints, to identify configurations where static keys are the only control.
Does middleBrick fix insecure webhook registrations found during a scan?
No. middleBrick detects and reports findings with remediation guidance; it does not modify configurations, block traffic, or automatically fix webhook settings.