Crlf Injection in Feathersjs with Cockroachdb
Crlf Injection in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when an attacker can inject a CRLF sequence (\r\n) into a header or log entry, causing the application to split and falsify records. In a Feathersjs application backed by Cockroachdb, the risk arises when user-controlled input flows into HTTP headers, log lines, or query parameters that are later reflected or stored. Feathersjs, being a flexible framework, does not inherently sanitize inputs used for headers or logging, and Cockroachdb, while strict in SQL semantics, does not prevent logical record splitting caused by injected control characters earlier in the request lifecycle.
Consider a Feathers service that builds a custom header using data from a Cockroachdb row. If an attacker can control a field such as display_name, they can inject \r\nSet-Cookie: malicious=value. Because Feathersjs may pass this value into an HTTP response header without validation, the injected CRLF can split the response and inject a new header. Cockroachdb itself is not the injection vector; the database stores the raw payload as provided. However, if the same data is later retrieved and used in logging or downstream protocols, the injected CRLF can break log parsers or be used to smuggle content across trust boundaries.
Another scenario involves logging and observability. Feathersjs may log request parameters or query results directly. If a query result from Cockroachdb includes CRLF characters (perhaps supplied by an end user during profile creation), and those results are written to logs without sanitization, an attacker can inject fake log entries or obscure real events. Because Cockroachdb does not treat newline characters as inherently dangerous, it stores and returns them, enabling log injection attacks. The framework’s default JSON serialization may also embed raw newlines in responses when reflection is enabled, compounding the risk.
In distributed tracing or error reporting, Feathersjs might forward request metadata to external services. If a field from Cockroachdb contains CRLF, these control characters can terminate headers prematurely in HTTP forwards, leading to response splitting or header smuggling. Even though Cockroachdb enforces strong typing and SQL correctness, it does not sanitize for protocol-level control characters, leaving the responsibility to the application layer.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on input validation, output encoding, and safe usage patterns when integrating Feathersjs with Cockroachdb. Always treat data from Cockroachdb as potentially unsafe when used in HTTP headers, logs, or downstream protocols, even if the database schema constrains format.
- Validate and sanitize inputs before persistence: Use allowlists to reject or strip CRLF characters in user-controlled fields. In Feathers, implement a custom hook that runs before
createorupdateto remove or encode \r and \n.
// src/hooks/sanitize-crlf.js
module.exports = function sanitizeCrlf() {
return async context => {
const sanitize = obj => {
if (typeof obj === 'string') {
return obj.replace(/\r?\n/g, ' ').replace(/\r/g, ' ');
}
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
for (const key of Object.keys(obj)) {
obj[key] = sanitize(obj[key]);
}
}
return obj;
};
if (context.data) {
context.data = sanitize(context.data);
}
if (context.params && context.params.query) {
// Optionally sanitize query filters if they influence headers/logging
}
return context;
};
};
- Use safe header construction: When building HTTP headers from Cockroachdb results, avoid concatenation with user data. If you must include dynamic values, map them to a safe registry or use constants rather than raw strings.
// src/services/account/service.js
const sanitizeForHeader = value => {
if (typeof value !== 'string') return value;
return value.replace(/[\r\n]+/g, '-');
};
app.service('account').hooks({
after: {
async create(context) {
const user = context.result;
// Example: setting a non-critical custom header safely
context.app.set('x-user-safe', sanitizeForHeader(user.display_name));
return context;
}
}
});
- Encode output when reflecting data: If responses include data from Cockroachdb that may contain newlines, apply JSON serialization consistently and avoid embedding raw rows in non-JSON formats. Ensure Content-Type headers are explicit and do not rely on client interpretation.
// src/hooks/encode-output.js
module.exports = function encodeOutput() {
return async context => {
const walk = obj => {
if (typeof obj === 'string') {
// Replace control characters that could break text protocols
return obj.replace(/[\x00-\x1F\x7F]/g, '');
}
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
for (const key of Object.keys(obj)) {
obj[key] = walk(obj[key]);
}
}
return obj;
};
if (context.result && typeof context.result === 'object') {
context.result = walk(context.result);
}
return context;
};
};
- Logging hygiene: Ensure logging libraries do not directly output raw rows from Cockroachdb. Transform newlines to escaped representations or remove them before writing to structured logs.
// src/logger.js
const pino = require('pino');
const logger = pino();
function safeLog(obj) {
if (typeof obj === 'string') {
return obj.replace(/[\r\n]+/g, '\\n');
}
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
const safe = {};
for (const [key, value] of Object.entries(obj)) {
safe[key] = safeLog(value);
}
return safe;
}
return obj;
}
// Usage after fetching from Cockroachdb
const row = await pool.query('SELECT display_name FROM profiles WHERE id = $1', [id]);
logger.info({ profile: safeLog(row.rows[0]) });