Container Escape in Feathersjs with Api Keys
Container Escape in Feathersjs with Api Keys — how this specific combination creates or exposes the vulnerability
A Container Escape in a Feathersjs application that relies on Api Keys occurs when an attacker who has obtained or guessed a valid key leverages it to reach beyond the intended service boundary. Feathersjs is often deployed in containerized environments, and if the API exposes endpoints that execute shell commands, spawn processes, or interact with the host filesystem, a compromised key can lead to host-level operations.
The risk is amplified when Feathersjs services run with elevated privileges inside containers or when containers share the host PID/network namespaces. An attacker can use the key to call a vulnerable service method that, for example, runs child_process.exec or child_process.spawn with unsanitized input. This can allow reading sensitive host files, pivoting to other containers, or executing arbitrary code on the host, effectively breaking container isolation.
Because middleBrick scans the unauthenticated attack surface, it can detect exposed management endpoints or misconfigured routes that might accept Api Keys but do not properly restrict operations. When combined with improper input validation, Api Keys can become a vector for command injection or path traversal that leads to container escape. The scanner flags related findings under BFLA/Privilege Escalation and Unsafe Consumption checks, highlighting risky patterns such as unchecked parameters passed to shell utilities.
In practice, this means a Feathersjs API with key-based access must never forward raw user input to system processes or expose endpoints that interact with the container host. Otherwise, a single leaked key may compromise the entire node environment, violating container security boundaries.
Api Keys-Specific Remediation in Feathersjs — concrete code fixes
To mitigate Container Escape risks when using Api Keys in Feathersjs, you should enforce strict input validation, avoid shell interactions, and apply least-privilege execution within containers. Below are concrete code examples demonstrating secure patterns.
1. Safe Api Key usage with input validation
Ensure every service method that uses Api Keys validates and sanitizes all inputs. Use a dedicated validation layer before any system interaction.
// src/hooks/validate-command.js
const { BadRequest } = require('@feathersjs/errors');
module.exports = function validateCommand() {
return context => {
const { data } = context;
if (data && data.command) {
// Allow only alphanumeric commands and safe arguments
const safeCommand = data.command.toString().replace(/[^a-zA-Z0-9_\-\s]/g, '');
if (!safeCommand || safeCommand !== data.command) {
throw new BadRequest('Invalid command format');
}
context.data.safeCommand = safeCommand;
}
return context;
};
};
// src/services/tasks/tasks.class.js
const { Service } = require('feathersjs');
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
class SafeTaskService extends Service {
async create(data, params) {
// Use the validated hook output instead of raw input
const cmd = data.safeCommand || 'default_safe_command';
const { stdout } = await execAsync(cmd);
return { output: stdout };
}
}
module.exports = function () {
const app = this;
app.use('/tasks', new SafeTaskService());
const tasksService = app.service('tasks');
tasksService.hooks({
before: {
create: [validateCommand()]
}
});
};
2. Avoid shell execution entirely — use internal logic
Replace child_process calls with internal logic or safe libraries. For example, use a configuration-driven approach instead of dynamic command execution.
// src/services/actions/actions.class.js
class ActionService {
constructor(options) {
this.actions = {
'sync-data': this.syncData,
'clear-cache': this.clearCache
};
}
async run(name, params) {
const action = this.actions[name];
if (!action) {
throw new Error('Action not allowed');
}
return action.call(this, params);
}
async syncData(params) {
// Internal safe implementation
return { status: 'synced' };
}
async clearCache(params) {
return { status: 'cleared' };
}
}
module.exports = function () {
const app = this;
const actionService = new ActionService();
app.use('/actions', actionService);
app.service('actions').hooks({
before: {
create: async context => {
const { name, params } = context.data;
context.data.result = await actionService.run(name, params);
delete context.data.name;
delete context.data.params;
return context;
}
}
});
};
3. Container-level protections
While not part of Feathersjs code, ensure containers run with non-root users and restrict capabilities. In your Dockerfile, avoid running as root and use read-only filesystems where possible:
# Dockerfile example
FROM node:18-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /app
COPY --chown=appuser:appgroup package*.json ./
RUN npm ci --only=production
COPY --chown=appuser:appgroup . .
CMD ["node", "src/index.js"]
These steps reduce the impact of a compromised Api Key and help prevent container escape.