Container Escape in Feathersjs with Dynamodb
Container Escape in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability
A container escape in a Feathersjs application using Dynamodb typically arises when an API exposes unsafe service methods or hooks that allow an attacker to break out of the expected execution context and interact with the host system or other containers. Feathersjs is a framework-agnostic REST and real-time API layer; when combined with Dynamodb as the persistence layer, the risk centers on how the service definitions, parameters, and hooks are constructed rather than the database itself.
Feathersjs services are defined by a service class or configuration that describes how data is created, read, updated, and deleted. If a service method accepts user-supplied input that is passed directly to lower-level operations without proper validation or sanitization, an attacker may supply specially crafted input that influences runtime behavior. For example, an improperly configured idParam or unguarded params.query can lead to unsafe DynamoDB operations such as scanning or traversing indexes that should be restricted.
In a containerized deployment, a compromised Feathersjs API can be leveraged to probe the runtime environment. If the application container has access to metadata endpoints, instance roles, or shared volumes, an attacker might use injection or insecure service patterns to retrieve sensitive credentials or execute unintended commands. While DynamoDB itself is a managed service and does not run processes inside containers, the application layer controlling access to DynamoDB can expose APIs that, when exploited, facilitate lateral movement or host interaction.
Specific patterns that increase risk include:
- Using raw
params.queryto build DynamoDB condition expressions without whitelisting fields, enabling injection into query logic. - Exposing service methods that accept dynamic table or index names derived from user input, which can lead to unauthorized data access across partitions.
- Misconfigured hooks that allow unauthenticated or elevated operations, bypassing intended authorization checks.
These patterns do not require deep knowledge of container internals; they exploit gaps in API design and input handling. middleBrick scans such configurations by running 12 security checks in parallel, including Input Validation, Authorization, and Unsafe Consumption, to detect risky parameter handling and overly permissive service definitions before an attacker can leverage them in a containerized environment.
Dynamodb-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on strict input validation, explicit parameter whitelisting, and defensive hook configuration. Avoid passing raw user input directly into DynamoDB condition expressions or key schemas. Instead, map user inputs to known-safe values and enforce strict type and pattern checks.
Below are concrete, working examples of Feathersjs service hooks and DynamoDB operations that mitigate container escape risks by ensuring user data never directly controls low-level API behavior.
1. Safe find with whitelisted query parameters
Ensure that queries against DynamoDB only use predefined, validated fields. Do not allow users to specify filter expressions or attribute names dynamically.
const { Service } = require('feathersjs');
const { DynamoDBDocumentClient, QueryCommand } = require('@aws-sdk/lib-dynamodb');
class SafeDynamoService {
constructor(options) {
this.options = options || {};
this.dynamoClient = options.dynamoClient || DynamoDBDocumentClient.from(new DynamoDBClient({}));
}
async find(params) {
const allowedFilters = ['status', 'createdAt'];
const filter = params.query && params.query.filter ? params.query.filter : {};
// Validate filter keys
const validFilter = Object.keys(filter).reduce((acc, key) => {
if (allowedFilters.includes(key)) {
acc[key] = filter[key];
}
return acc;
}, {});
const command = new QueryCommand({
TableName: 'Items',
KeyConditionExpression: 'id = :id',
ExpressionAttributeValues: {
':id': params.query.id
},
FilterExpression: Object.keys(validFilter).map(k => `${k} = :${k}`).join(' AND '),
ExpressionAttributeValues: {
...ExpressionAttributeValues,
...Object.keys(validFilter).reduce((acc, k) => {
acc[`:${k}`] = validFilter[k];
return acc;
}, {})
}
});
const result = await this.dynamoClient.send(command);
return { total: result.Count, data: result.Items };
}
}
const service = new SafeDynamoService({ dynamoClient });
module.exports = function () {
const app = this;
app.use('/items', new Service({
async get(id, params) {
const command = new GetItemCommand({
TableName: 'Items',
Key: { id }
});
const data = await service.dynamoClient.send(command);
return data.Item;
},
async find(params) {
return service.find(params);
}
}));
};
2. Hook-level validation and metadata isolation
Use before hooks to sanitize inputs and avoid passing raw params.query into DynamoDB operations. This prevents attackers from injecting unexpected expressions through query parameters.
const feathersHooks = require('feathers-hooks-common');
app.service('records').hooks({
before: {
async create(hook) {
const allowedFields = ['name', 'type', 'tags'];
const data = hook.data;
const sanitized = {};
allowedFields.forEach(field => {
if (data[field] !== undefined) {
sanitized[field] = data[field];
}
});
hook.data = sanitized;
},
async remove(id, params) {
// Ensure id is a string matching expected pattern
if (!/^[a-zA-Z0-9_-]+$/.test(id)) {
throw new Error('Invalid identifier');
}
params.rawId = id;
}
}
});
// Custom adapter example using safe params
app.use('/records', new Service({
async remove(id, params) {
const command = new DeleteItemCommand({
TableName: 'Records',
Key: { id }
});
return this.dynamoClient.send(command);
}
}));
3. Enforce least privilege and avoid dynamic table references
Never construct table or index names from user input. Use environment variables or service configuration to define resource identifiers. If your design requires multiple tables, map user input to a controlled lookup rather than direct concatenation.
const TABLE_MAP = {
user: process.env.USER_TABLE,
log: process.env.LOG_TABLE
};
async function safeGetTable(entityType, id) {
if (!TABLE_MAP[entityType]) {
throw new Error('Unsupported entity type');
}
const command = new GetItemCommand({
TableName: TABLE_MAP[entityType],
Key: { id }
});
const result = await dynamoClient.send(command);
return result.Item;
}
// In service method
app.use('/entities', {
async get(id, params) {
const entityType = params.query._entityType;
return safeGetTable(entityType, id);
}
});
By combining these patterns—whitelisted filters, hook-based sanitization, and static table mapping—you reduce the attack surface that could be leveraged for container escape. middleBrick’s checks for Input Validation and Authorization help identify insecure service configurations before deployment.