Excessive Data Exposure in Feathersjs with Api Keys
Excessive Data Exposure in Feathersjs with Api Keys
Excessive Data Exposure occurs when an API returns more information than necessary for a given operation. In FeathersJS applications that rely on API keys for access control, this often arises from misconfigured service hooks, missing field filtering, or overly permissive service methods. When API keys are used for authentication, developers sometimes assume that the key alone is sufficient to enforce data-level restrictions. This assumption can lead to endpoints returning entire database records, including sensitive fields such as password hashes, internal identifiers, or private metadata, even when the client should only see a subset of that data.
FeathersJS is built around services and hooks, and it does not automatically filter response fields. If a service retrieves a document and sends it back without explicit projection, all fields are exposed. When combined with API key authentication, this becomes risky because the API key may grant access to a broader set of data than intended. For example, an API key issued to a third-party integration might allow read access to user profiles, but the integration should only receive display name and email, not internal IDs or roles. Without explicit field filtering in the service configuration or hooks, the full record is returned, resulting in Excessive Data Exposure.
Another common pattern involves query parameters that should limit returned fields, such as $select or fields, being ignored or not enforced by the service. In FeathersJS, if a client sends a query like ?$select=name,email, the service must be configured to respect this parameter. If the service uses a custom handler or bypasses the default query behavior, the $select may be silently ignored, and the full document is returned. When API keys are used, monitoring these vectors becomes critical because the key may be associated with a client that should not have visibility into certain fields.
The interaction between API keys and unvalidated input also contributes to this vulnerability. For instance, if a service method accepts user-controlled input to construct a response and does not sanitize or limit the output, an attacker with a valid API key might coerce the endpoint to return data belonging to other resources. This can happen when the service uses the API key to identify the requester but fails to scope the data query correctly. For example, a "list items" endpoint might return all items in a collection rather than only those associated with the requester’s allowed scope, especially if the underlying database query lacks proper ownership checks.
Real-world attack patterns include enumeration via differences in response size or timing, where an attacker uses a valid API key to infer the existence of hidden fields or related resources. If a response includes fields such as resetToken, internalNotes, or permissions unintentionally, this can reveal information useful for further exploitation. OWASP API Top 10 category A5:2023, Security Misconfiguration, aligns with this risk, as missing data filtering is a form of misconfiguration. MiddleBrick’s LLM/AI Security checks specifically test for system prompt leakage and output exposure, but data exposure in business logic requires deliberate service configuration to prevent.
In FeathersJS, services defined in src/services/ often look like this by default:
// src/services/items/items.class.js
class ItemsService {
find(params) {
return Promise.resolve([
{ id: 1, name: 'Public Item', internalCode: 'SECRET-123' },
{ id: 2, name: 'Restricted Item', internalCode: 'SECRET-456' }
]);
}
}
module.exports = function (app) {
app.use('/items', new ItemsService());
};
If this service is protected only by an API key and no field filtering is applied, every client with a valid key can see internalCode. This exemplifies Excessive Data Exposure: the API key provides access, but the service returns more data than necessary. Proper mitigation requires explicit field selection and scope enforcement within the service logic or hooks.
Api Keys-Specific Remediation in Feathersjs
Remediation focuses on ensuring that API key usage does not inadvertently expose excessive data. This involves configuring services to respect field selection, validating query parameters, and scoping data access based on the key’s permissions. Below are concrete code examples demonstrating secure patterns.
1. Enabling $select support in service queries
Ensure that your FeathersJS service respects the $select query parameter. The default Feathers adapter usually supports this, but custom hooks or overrides may disable it. Explicitly allow projection in your service configuration.
// src/services/items/items.class.js
class ItemsService {
find(params) {
const { $select } = params.query;
let items = [
{ id: 1, name: 'Public Item', internalCode: 'SECRET-123', ownerId: 'user1' },
{ id: 2, name: 'Restricted Item', internalCode: 'SECRET-456', ownerId: 'user2' }
];
if ($select) {
const fields = $select.split(',');
items = items.map(item => {
const filtered = {};
fields.forEach(field => {
if (item.hasOwnProperty(field)) {
filtered[field] = item[field];
}
});
return filtered;
});
}
return Promise.resolve(items);
}
}
module.exports = function (app) {
app.use('/items', new ItemsService());
};
2. Scoping data access with API key metadata
Assume each API key is associated with a scope or tenant. In the service, use the key metadata to filter results. This requires storing key-to-scope mappings externally and reading them during request processing.
// src/services/data/data.class.js
class DataService {
async find(params) {
const apiKey = params.query.apiKey || params.headers['x-api-key'];
const scope = await getScopeForApiKey(apiKey); // e.g., returns { allowedTenant: 'acme' }
// Simulated database query with scope filter
return db.query('SELECT id, name, publicData FROM records WHERE tenantId = ?', [scope.allowedTenant]);
}
}
// Mock helper
async function getScopeForApiKey(key) {
const mapping = { 'key-public': { allowedTenant: 'public' }, 'key-internal': { allowedTenant: 'internal' } };
return mapping[key] || { allowedTenant: 'public' };
}
module.exports = function (app) {
app.use('/data', new DataService());
};
3. Using hooks to strip sensitive fields
Leverage FeathersJS hooks to remove sensitive fields from the response before it leaves the server. This ensures that even if a service returns full records, the response is sanitized.
// src/hooks/strip-sensitive.js
module.exports = function () {
return async context => {
if (context.result && context.result.data) {
context.result.data = context.result.data.map(item => {
const { passwordHash, internalNotes, ...safeItem } = item;
return safeItem;
});
}
return context;
};
};
// In service configuration
// src/services/users/users.hooks.js
const stripSensitive = require('./hooks/strip-sensitive');
module.exports = {
before: {},
after: { all: [stripSensitive()] },
error: {}
};
4. Validating and normalizing query parameters
Explicitly validate incoming query parameters to prevent bypass attempts. Reject unknown parameters and normalize field lists to avoid injection of malicious query syntax.
// src/services/reports/reports.class.js
class ReportsService {
find(params) {
const allowedSelect = ['id', 'title', 'summary'];
let selectFields = params.query.$select;
if (selectFields) {
const requested = selectFields.split(',').map(f => f.trim());
const valid = requested.filter(f => allowedSelect.includes(f));
selectFields = valid.length ? valid.join(',') : allowedSelect[0];
}
// Proceed with filtered query
return Promise.resolve([{ id: 1, title: 'Summary', details: 'Internal' }]);
}
}
module.exports = function (app) {
app.use('/reports', new ReportsService());
};
These patterns ensure that API key usage does not amplify data exposure. By combining service-level filtering, scope enforcement, and response sanitization, you reduce the risk of returning more data than necessary.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |