Integrity Failures in Feathersjs
How Integrity Failures Manifests in Feathersjs
Integrity Failures in Feathersjs applications occur when users can manipulate data they shouldn't have access to, typically through IDOR (Insecure Direct Object Reference) vulnerabilities. In Feathersjs, this commonly manifests through service operations where the user ID is either missing from queries or improperly validated.
The most frequent pattern involves service methods like find, get, update, and patch where the user can specify arbitrary IDs in query parameters. For example, a REST endpoint like /messages/123 might allow any authenticated user to retrieve or modify message 123, regardless of whether they own it.
// Vulnerable Feathersjs service method
class MessageService {
async get(id, params) {
// No user ID validation - any user can access any message
return await this._get(id, params);
}
async update(id, data, params) {
// No ownership check - user can update any message
return await this._patch(id, data, params);
}
}
Another common manifestation occurs with query parameter manipulation. Feathersjs services accept query objects that can be manipulated to bypass authorization. A user might modify the userId field in a query to access another user's data:
// Vulnerable query manipulation
const params = {
query: {
userId: 999 // User manually changes to access another user's data
}
};
Feathersjs's real-time features also introduce integrity risks. When using app.channel() without proper filtering, users might receive events about data they shouldn't see:
// Vulnerable real-time channel
app.service('messages').publish((data, context) => {
// Broadcasts to all connected clients - no filtering
return app.channel("anonymous");
});
Property authorization failures are particularly problematic in Feathersjs. The framework's flexible data model means that sensitive properties might be exposed through service methods without proper access controls:
// Vulnerable property exposure
class UserService {
async find(params) {
// Returns all user properties including sensitive data
return await this._find(params);
}
}
Feathersjs-Specific Detection
Detecting Integrity Failures in Feathersjs requires examining both the service code and runtime behavior. Start by auditing your service methods for missing authorization checks. Look for service methods that accept IDs or query parameters without validating the requesting user's permissions.
middleBrick's black-box scanning approach is particularly effective for Feathersjs applications. The scanner tests unauthenticated attack surfaces by sending requests with manipulated IDs and query parameters to identify BOLA/IDOR vulnerabilities. For a Feathersjs API at https://api.example.com, middleBrick would:
- Map the API surface by discovering all available endpoints
- Test each endpoint with manipulated IDs (incrementing numbers, common IDOR patterns)
- Analyze responses for data leakage or unauthorized access
- Check for missing authentication requirements on sensitive endpoints
- Validate property authorization by requesting data with different user contexts
middleBrick's Feathersjs-specific detection includes checking for common patterns like:
// What middleBrick scans for:
// 1. Missing user ID in service method calls
// 2. Unfiltered query parameters
// 3. Broad real-time channels without user filtering
// 4. Exposed sensitive properties in service responses
Manual code review should focus on Feathersjs's authentication hooks and service methods. Use middleBrick's CLI to scan your API during development:
npm install -g middlebrick
middlebrick scan https://api.example.com/messages
For continuous security, integrate middleBrick into your Feathersjs CI/CD pipeline using the GitHub Action. This ensures new endpoints are automatically scanned for integrity failures before deployment:
# .github/workflows/security.yml
name: API Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run middleBrick Scan
uses: middlebrick/middlebrick-action@v1
with:
target: https://staging-api.example.com
fail-on-severity: high
Feathersjs-Specific Remediation
Remediating Integrity Failures in Feathersjs requires implementing proper authorization at the service level. The most effective approach is using Feathersjs's built-in authentication hooks and custom authorization logic.
For basic ownership validation, use the authenticate hook combined with custom logic:
const { authenticate } = require('@feathersjs/authentication').hooks;
const { iff, isProvider, fastJoin } = require('feathers-hooks-common');
class MessageService {
async get(id, params) {
// Ensure user can only access their own messages
const userId = params.user?.id;
const message = await this._get(id, params);
if (message.userId !== userId) {
throw new Error('Access denied');
}
return message;
}
async update(id, data, params) {
const userId = params.user?.id;
const message = await this._get(id, params);
if (message.userId !== userId) {
throw new Error('Access denied');
}
return await this._patch(id, data, params);
}
}
For more scalable solutions, implement authorization hooks that automatically filter data based on the authenticated user:
const { authenticate } = require('@feathersjs/authentication').hooks;
const { iff, isProvider, fastJoin } = require('feathers-hooks-common');
const userFilter = () => context => {
const { type, params, method } = context;
if (type === 'before' && params.user) {
context.params.query = {
...context.params.query,
userId: params.user.id
};
}
};
module.exports = {
before: {
all: [authenticate('jwt'), userFilter()],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
};
Property authorization can be handled using Feathersjs's population and serialization features:
const { fastJoin } = require('feathers-hooks-common');
const protectedProperties = {
joins: {
excludeSensitive: () => async (recs, context) => {
if (Array.isArray(recs)) {
recs.forEach(rec => {
if (rec.user) {
delete rec.user.password;
delete rec.user.sensitiveData;
}
});
}
}
}
};
module.exports = {
before: {
all: [authenticate('jwt')]
},
after: {
all: [fastJoin(protectedProperties)]
}
};
For real-time integrity, implement proper channel filtering:
app.service('messages').publish((data, context) => {
// Only send to users who own the data or have permission
if (data.userId === context.user?.id) {
return app.channel(`userIds/${context.user.id}`);
}
// Or return empty channel for unauthorized users
return app.channel();
});