Data Exposure in Feathersjs with Api Keys
Data Exposure in Feathersjs with Api Keys — how this specific combination creates or exposes the vulnerability
FeathersJS is a flexible framework for building REST and real-time APIs. When developers use API keys for authentication, data exposure can occur if keys are handled inconsistently across services, hooks, and transport layers. A common pattern is to store the key in headers or query parameters and validate it in a custom hook or service middleware. If this validation is incomplete or bypassed, sensitive resources can be returned to unauthenticated or low-privileged callers.
Consider a service that manages internal billing records. A developer adds an API key hook to the service but only checks for the presence of a key, not its scope or association with the requested resource. If the hook does not enforce row-level constraints, an attacker who knows or guesses a valid key can request /billing?userId=other and receive records that do not belong to them. This is a broken object-level authorization pattern enabled by weak key validation, leading to data exposure of private financial data.
Another scenario involves real-time channels. FeathersJS allows publish/subscribe semantics, and API keys can be used to authorize channel joins. If the key is validated only once during connection establishment and then reused for all subscriptions without rechecking dataset ownership, subscribers may receive messages from channels they should not see. For example, a key issued to a partner integration might permit access to a general channel, but if the channel emits messages containing personally identifiable information (PII) or internal operational details, those data are exposed to the partner beyond the intended scope.
Transport configuration also plays a role. If an API exposes both HTTP and WebSocket transports and keys are only verified on HTTP routes, WebSocket events may rely on the initial handshake state without revalidating key permissions for each message. An attacker who compromises a key with limited HTTP rights might leverage the WebSocket path to subscribe to admin-only updates, resulting in data exposure of configuration changes or sensitive events. Additionally, misconfigured CORS or proxy headers can cause the API to appear on origins not intended for key-bearing requests, widening the exposure surface.
Middleware ordering is another contributor. In FeathersJS, hooks execute in a defined sequence. If a hook that enforces API key validation is placed after a hook that formats or enriches the response, sensitive fields may be added to the result before authorization is checked. This can inadvertently include internal identifiers, debug data, or relationship references that should not be returned to the caller. By aligning hook order so that authorization precedes response assembly, developers reduce the risk of data exposure through incomplete filtering.
Api Keys-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on strict key validation, scope enforcement, and transport consistency. Always validate the API key early in the hook chain and ensure the key’s permissions match the requested resource. Avoid relying on key presence alone; bind the key to an allowed set of operations or records and revalidate where necessary.
Example of a robust custom hook that validates an API key and enforces tenant isolation to prevent data exposure:
// src/hooks/api-key-auth.js
module.exports = function apiKeyAuth(options = {}) {
return async context => {
const { app, params } = context;
const key = (params.headers && params.headers['x-api-key']) || (params.query && params.query.api_key);
if (!key) {
throw new Error('Unauthorized: API key missing');
}
// Fetch the key record from a store (e.g., a database service)
const keyRecord = await app.service('api-keys').get(key, params);
if (!keyRecord || keyRecord.expiresAt < new Date()) {
throw new Error('Unauthorized: Invalid or expired API key');
}
// Enforce scope: ensure the key is allowed for this service and operation
const allowedServices = keyRecord.scopes ? keyRecord.scopes.split(',') : [];
const serviceName = context.path;
if (!allowedServices.includes(serviceName)) {
throw new Error('Forbidden: Key not scoped for this service');
}
// Enforce resource-level constraints (e.g., tenant or userId)
if (context.params.query) {
// Ensure queries are restricted to the principal allowed by the key
context.params.query.userId = keyRecord.userId;
}
// Attach key metadata for downstream use, but do not rely on it for authorization alone
context.params.authApiKey = keyRecord;
return context;
};
};
Apply the hook at the service level in your FeathersJS server setup:
// src/app.js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const apiKeyAuth = require('./hooks/api-key-auth');
const app = express(feathers());
// Configure services
app.use('/billing', require('./services/billing'), {
before: {
all: [apiKeyAuth({ strict: true })],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
});
// Ensure WebSocket transport uses the same hook via a channel hook
app.publish((data, hook) => {
// Re-validate key permissions inside channel logic if needed
const keyRecord = hook.params.authApiKey;
if (!keyRecord || !keyRecord.scopes.includes('realtime')) {
return []; // Do not publish to any channel
}
return app.channel('general');
});
For real-time channels, validate permissions on each message or subscription by inspecting the key’s scopes and the channel topic. Do not assume initial handshake authorization covers all subsequent events. If a key is limited to specific channels, enforce that in the publish callback and reject unauthorized subscriptions explicitly.
Additionally, standardize transport security. Use the same validation strategy across HTTP and WebSocket paths, and avoid differing authorization rules. Configure CORS to allow only trusted origins, and ensure proxy headers do not inadvertently expose APIs to unintended networks. These measures reduce data exposure by ensuring API keys are checked consistently and that responses contain only data the key is explicitly permitted to access.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |