Sandbox Escape in Feathersjs
How Sandbox Escape Manifests in Feathersjs
Feathersjs applications can suffer from sandbox escape vulnerabilities when improperly handling user-controlled data that flows through the framework's service layer. This often occurs when developers use app.set() or app.get() to store sensitive configuration data, then expose it through services without proper authorization checks.
A common pattern involves storing API keys, database credentials, or JWT secrets in the application configuration, then creating a service that returns this data based on user input. For example:
const app = feathers();
app.set('stripeSecretKey', process.env.STRIPE_SECRET_KEY);
app.service('config').find((context) => {
return {
stripeKey: app.get('stripeSecretKey')
};
});This creates a sandbox escape vulnerability because any authenticated user can access the /config endpoint and retrieve the Stripe secret key, escaping the intended security boundary.
Another manifestation occurs with Feathersjs's dynamic service registration. Developers might create services based on user input without proper validation:
app.use(`/${params.service}`, new Service());If params.service comes from user input and isn't validated, attackers can register malicious services or access internal services by guessing their names.
Property-based sandbox escape is also prevalent in Feathersjs. When using params.query or params.data directly in database queries without sanitization, attackers can escape the intended query constraints:
// Vulnerable: direct query parameter usage
app.service('users').find((context) => {
return context.app.service('users').find({
query: context.params.query
});
});This allows attackers to modify query parameters like $select, $limit, or even inject MongoDB operators to access data they shouldn't see.
Feathersjs-Specific Detection
Detecting sandbox escape vulnerabilities in Feathersjs requires examining both the application code and runtime behavior. Start by auditing your service definitions for direct access to app.get() or app.set() within service methods. Any service that exposes configuration data without proper authorization is a red flag.
Look for patterns where services accept arbitrary input for service names, database collections, or query parameters. Use static analysis tools to find:
- Direct usage of
app.get()in service hooks or methods - Dynamic service registration based on user input
- Unvalidated
params.queryusage in database operations - Services that return entire
appobjects or configuration
Runtime detection can be performed using middleBrick's API security scanner, which specifically tests for sandbox escape patterns in Feathersjs applications. The scanner checks for:
middlebrick scan https://yourapp.com --test sandbox-escapemiddleBrick tests for sandbox escape by attempting to access configuration endpoints, probing for service enumeration vulnerabilities, and checking for improper data exposure through Feathersjs's service architecture. It specifically looks for:
- Unauthenticated access to configuration data
- Service name injection vulnerabilities
- Property-based query manipulation
- Excessive data exposure through find operations
The scanner provides a security score (A-F) and specific findings with remediation guidance tailored to Feathersjs's architecture.
Feathersjs-Specific Remediation
Remediating sandbox escape vulnerabilities in Feathersjs requires a defense-in-depth approach. First, implement proper authorization at the service level using Feathersjs's built-in hooks system:
const { authenticate } = require('@feathersjs/authentication').hooks;
const { iff, isProvider, fastJoin } = require('feathers-hooks-common');
const protectConfig = fastJoin({
joins: {
config: () => async (context) => {
// Only allow access to config for admin users
if (!context.params.user || !context.params.user.isAdmin) {
throw new Error('Not authorized');
}
}
}
});
app.service('config').hooks({
before: {
find: [authenticate('jwt'), protectConfig]
}
});For dynamic service registration, validate service names against a whitelist:
const validServices = ['users', 'posts', 'comments'];
app.use(`/${serviceName}`, new Service(), {
whitelist: validServices.includes(serviceName) ? validServices : []
});Sanitize query parameters using Feathersjs's query sanitization features:
const sanitizeQuery = (query) => {
const allowedOperators = ['$gt', '$lt', '$gte', '$lte'];
const sanitized = {};
for (const [key, value] of Object.entries(query)) {
if (key.startsWith('$')) {
if (!allowedOperators.includes(key)) continue;
}
sanitized[key] = value;
}
return sanitized;
};
app.service('users').find((context) => {
const safeQuery = sanitizeQuery(context.params.query);
return context.app.service('users').find({ query: safeQuery });
});Implement data access controls using Feathersjs's populate hook to limit exposed fields:
const limitFields = hook => {
if (hook.method === 'find') {
hook.params.sequelize = {
attributes: ['id', 'name', 'email'] // Only allow these fields
};
}
};
app.service('users').hooks({
before: {
find: [limitFields]
}
});For comprehensive protection, integrate middleBrick's continuous monitoring into your Feathersjs application's CI/CD pipeline:
# .github/workflows/security.yml
name: API Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run middleBrick Scan
run: |
npx middlebrick scan https://staging.yourapp.com --fail-below BThis ensures sandbox escape vulnerabilities are caught before deployment and maintains your API's security posture over time.