Stack Overflow in Feathersjs
How Stack Overflow Manifests in Feathersjs
Stack overflow vulnerabilities in Feathersjs applications typically occur when recursive operations or deeply nested data structures cause the Node.js call stack to exceed its limits. In Feathersjs, this commonly manifests through service methods that process hierarchical data, recursive hooks, or infinite loops in custom business logic.
A classic Feathersjs-specific scenario involves recursive service calls within hooks. Consider a populateParent hook that recursively fetches parent entities:
const populateParent = async (context) => {
if (context.result.parentId) {
const parent = await context.app.service('entities').get(context.result.parentId);
context.result.parent = parent;
// Recursive call without proper base case
await populateParent({ result: parent });
}
return context;
};This hook will cause a stack overflow when processing entities with deep parent hierarchies. The recursive populateParent call continues indefinitely until Node.js throws a RangeError: Maximum call stack size exceeded.
Another Feathersjs-specific pattern occurs with event-driven recursion. When using pubsub or real-time features:
const handleUpdate = async (data) => {
// Event triggers another update, creating recursion
await context.app.service('items').patch(data.id, { processed: true });
// This can trigger the same hook again
};Feathersjs's event system can create feedback loops where updates trigger hooks that trigger more updates, eventually exhausting the stack.
Database query patterns also contribute to stack overflow risks. Using recursive Common Table Expressions (CTEs) without depth limits in PostgreSQL queries executed through Feathersjs services can cause excessive recursion:
const getHierarchy = async (id, depth = 0) => {
if (depth > 1000) return []; // Still risky
const result = await sequelize.query(`
WITH RECURSIVE hierarchy AS (
SELECT * FROM items WHERE id = $1
UNION ALL
SELECT i.* FROM items i
JOIN hierarchy h ON i.parent_id = h.id
)
SELECT * FROM hierarchy
`, { bind: [id] });
return result;
};Without proper depth limiting or iterative approaches, these queries can process extremely deep hierarchies that overwhelm the call stack during result processing.
Feathersjs-Specific Detection
Detecting stack overflow vulnerabilities in Feathersjs applications requires both static code analysis and runtime monitoring. The most effective approach combines automated scanning with manual code review of recursive patterns.
middleBrick's black-box scanning can identify potential stack overflow vulnerabilities by testing service endpoints with deeply nested data structures. The scanner sends requests with maximum recursion depths and monitors for stack overflow errors or timeouts:
middlebrick scan https://api.example.com/users
--recursive-depth 50
--max-nesting 20
--timeout 30sThis configuration tests how your Feathersjs services handle extreme nesting scenarios. The scanner specifically looks for:
- Recursive service calls in hooks that don't have proper termination conditions
- Infinite loops in custom business logic
- Deeply nested data structures that exceed typical processing limits
- Event-driven recursion through Feathersjs's pubsub system
For source code analysis, examine your Feathersjs hooks and services for these patterns:
// Dangerous: no depth limiting
const recursiveHook = async (context) => {
if (shouldContinue(context)) {
await context.app.service('items').find({ query: { parentId: context.id } });
return recursiveHook(context); // Stack overflow risk
}
return context;
};middleBrick's OpenAPI analysis also checks for recursive schema definitions in your API specifications. If your Swagger/OpenAPI specs contain circular references without depth limits, this indicates potential stack overflow risks in your Feathersjs implementation.
Runtime monitoring can catch stack overflow attempts before they cause crashes. Implement error tracking that specifically monitors for RangeError: Maximum call stack size exceeded errors and logs the request context that triggered them.
Feathersjs-Specific Remediation
Remediating stack overflow vulnerabilities in Feathersjs requires both architectural changes and defensive coding practices. The most effective approach combines iterative processing with proper depth limiting.
Replace recursive hooks with iterative approaches using async iterators:
const populateHierarchy = async (context) => {
const stack = [context.result];
const processed = new Set();
while (stack.length > 0) {
const current = stack.pop();
if (processed.has(current.id) || current.depth > 20) {
continue; // Depth limit prevents stack overflow
}
processed.add(current.id);
// Process children iteratively
const children = await context.app.service('items')
.find({ query: { parentId: current.id } });
for (const child of children) {
child.depth = (current.depth || 0) + 1;
stack.push(child);
}
}
return context;
};This iterative approach uses a controlled stack data structure instead of the call stack, eliminating the risk of stack overflow while maintaining the same functionality.
For database operations, use iterative CTE processing or LIMIT/OFFSET pagination instead of recursive queries:
const getHierarchyIteratively = async (id, maxDepth = 20) => {
let results = [];
let currentLevel = [{ id }];
let depth = 0;
while (currentLevel.length > 0 && depth < maxDepth) {
const currentIds = currentLevel.map(item => item.id);
const nextLevel = await context.app.service('items')
.find({ query: { parentId: { $in: currentIds } } });
results = results.concat(nextLevel);
currentLevel = nextLevel;
depth++;
}
return results;
};Implement depth limiting in your Feathersjs service schemas using custom validation:
const depthLimitedSchema = (maxDepth) => ({
validate: (data, params) => {
if (data.depth && data.depth > maxDepth) {
throw new Error(`Maximum depth of ${maxDepth} exceeded`);
}
return data;
}
});Add error handling middleware to gracefully handle stack overflow attempts:
app.hooks({
error: async (context) => {
if (context.error instanceof RangeError &&
context.error.message.includes('Maximum call stack size')) {
// Log the incident and return a safe response
console.warn('Stack overflow attempt detected', {
url: context.params.url,
method: context.method,
data: context.data
});
context.result = { error: 'Request processing limit exceeded' };
context.error = null; // Suppress the error
}
}
});Finally, use Feathersjs's built-in validation and sanitization hooks to limit input complexity before processing:
const sanitizeInput = async (context) => {
if (context.data && typeof context.data === 'object') {
// Limit object depth and size
context.data = JSON.parse(JSON.stringify(context.data, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (Object.keys(value).length > 100) {
return 'RECURSION_LIMITED';
}
}
return value;
}));
}
return context;
};Frequently Asked Questions
How can I test my Feathersjs application for stack overflow vulnerabilities?
middlebrick scan https://your-api.com --recursive-depth 50 will test how your services handle deeply nested data structures. Additionally, manually review your hooks and services for recursive patterns without depth limiting.What's the difference between stack overflow and memory exhaustion in Feathersjs?
RangeError. Memory exhaustion happens when your application consumes too much heap memory, typically from processing large datasets or holding onto references. Stack overflow is usually a control flow issue, while memory exhaustion is a resource consumption issue.