Log Injection in Feathersjs with Dynamodb
Log Injection in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability
Log injection occurs when untrusted data is written directly into log entries without sanitization, enabling an attacker to forge log lines, obscure real events, or trigger log-based attacks such as log poisoning or log injection via newline characters. In a Feathersjs application using DynamoDB as the persistence layer, this risk arises when request data—such as user IDs, query parameters, or dynamic filter values—is passed into logging statements or error messages that are later written to logs. Because DynamoDB does not perform log formatting or sanitization, the application must ensure that any data destined for logs is validated and escaped before emission.
Consider a Feathersjs service that retrieves an item by ID and logs the incoming id parameter directly:
app.service('todos').before({ find: (context) => {
const { id } = context.params.query;
console.log('Fetching todo item with id:', id);
return context;
}});
If an attacker supplies an id value containing newline or control characters (e.g., \n{"level":"ERROR"} "System compromised"), the log entry can be corrupted, making it difficult to distinguish legitimate events from injected content. This becomes especially problematic when logs are aggregated and analyzed automatically, as injected newlines can split one log event into multiple entries or forge log metadata.
Feathersjs hooks often interact with DynamoDB via the AWS SDK. If the SDK response or error messages are logged verbatim, attacker-controlled data within error objects (such as a ConditionalCheckFailedException message) may also be reflected in logs. For example:
const dynamodb = new AWS.DynamoDB.DocumentClient();
app.service('records').hooks({
after: {
async remove(context) {
try {
await dynamodb.delete({ TableName: context.params.tableName, Key: context.id }).promise();
} catch (error) {
console.error('Deletion failed:', error.message);
}
}
}
});
If error.message contains user-influenced strings, the log line can be altered. Because DynamoDB errors may include request identifiers or conditional check details, failing to sanitize these fields before logging can expose internal logic or aid in log injection. The combination of Feathersjs hooks, DynamoDB responses, and insufficient input validation creates a scenario where log integrity cannot be guaranteed.
Additionally, log injection can affect observability and monitoring. Corrupted logs may hinder detection of genuine incidents, and in extreme cases, facilitate further attacks by misleading administrators. Mitigation requires validating and sanitizing all data written to logs, applying consistent encoding for newlines and control characters, and ensuring that logging practices are centralized and reviewed as part of routine security assessments.
Dynamodb-Specific Remediation in Feathersjs — concrete code fixes
To remediate log injection when using Feathersjs with DynamoDB, sanitize and encode any data that may be included in logs. This includes user input, DynamoDB error messages, and dynamic identifiers. Use a structured logging approach that separates log metadata from message content, and avoid interpolating raw data directly into log strings.
First, validate and sanitize identifiers before logging. For example, ensure IDs conform to an expected pattern and escape control characters:
function safeLogId(id) {
if (typeof id !== 'string' || !/^[a-zA-Z0-9_-]{1,128}$/.test(id)) {
return 'invalid_id';
}
return id.replace(/[\r\n]+/g, '_');
}
app.service('todos').before({ find: (context) => {
const { id } = context.params.query;
console.log('Fetching todo item with id:', safeLogId(id));
return context;
}});
Second, handle DynamoDB errors safely by extracting only sanitized fields:
const dynamodb = new AWS.DynamoDB.DocumentClient();
app.service('records').hooks({
after: {
async remove(context) {
try {
await dynamodb.delete({ TableName: context.params.tableName, Key: context.id }).promise();
} catch (error) {
const safeMessage = (error.message || '').replace(/[\r\n]+/g, ' ').substring(0, 200);
console.error('Deletion failed:', safeMessage);
}
}
}
});
Third, use structured logging to avoid concatenation issues. Emit JSON logs with clearly separated fields so log parsers can reliably distinguish entries:
const safeLogObject = (obj) => JSON.stringify({
timestamp: new Date().toISOString(),
level: 'info',
event: 'fetch_todo',
id: safeLogId(obj.id || '')
});
app.service('todos').before({ find: (context) => {
console.log(safeLogObject({ id: context.params.query.id }));
return context;
}});
Finally, apply consistent sanitization across all DynamoDB interactions. Create a utility that wraps logging for service methods, ensuring that any data written to logs is normalized for newlines and other control characters. This reduces the risk of log injection across the codebase and aligns logging behavior with security best practices.