Path Traversal in Hapi with Cockroachdb
Path Traversal in Hapi with Cockroachdb — how this specific combination creates or exposes the vulnerability
A path traversal vulnerability occurs when user-controlled data is used to construct file system paths or, in the case of APIs interfacing with databases, influences identifiers or lookup paths that can lead to unauthorized access. In a Hapi application using CockroachDB, the risk typically arises when request parameters (such as an id, fileId, or name) are directly interpolated into SQL statements without proper validation or parameterization, or when these values influence dynamic paths that later interact with the database.
Consider a route designed to fetch user profile data where the username is used both as a logical key in CockroachDB and as part of a file path for profile assets. If the route handler uses string concatenation or insecure template literals to build a query, an attacker can supply a crafted input such as ../../../etc/passwd or a specially crafted string that alters the intended query logic. While CockroachDB does not directly expose a classic file system path, the injection can manipulate row-level identifiers or conditional logic, effectively traversing intended data boundaries.
For example, a vulnerable Hapi route might look like this:
const Hapi = require('@hapi/hapi');
const { Pool } = require('pg');
const init = async () => {
const server = Hapi.server({ port: 3000, host: 'localhost' });
server.route({
method: 'GET',
path: '/user/{username}',
handler: async (request, h) => {
const { username } = request.params;
const pool = new Pool({ connectionString: 'postgresql://user:pass@cockroachdb-host:26257/mydb' });
const query = 'SELECT * FROM users WHERE username = \'' + username + '\'';
const result = await pool.query(query);
return result.rows[0];
}
});
await server.start();
};
init();
In this snippet, the username parameter is directly concatenated into the SQL string. An attacker could send a request to /user/admin%27%20OR%201=1;%20--, leading to unintended data exposure. Even though CockroachDB uses a PostgreSQL-wire compatible protocol, the vulnerability is not in the database itself but in how the application builds and executes queries. A malicious payload could extract other users’ records or infer schema details. The same pattern applies if the username is used to construct a dynamic path for a secondary lookup, such as loading a user-specific configuration file from disk, where traversal sequences could escape the intended directory.
Another scenario involves numeric identifiers that are assumed to be safe but are cast to strings and concatenated. For instance, an API endpoint like /files/{fileId} might use fileId to build a path such as /data/files/${fileId}. If fileId is not strictly validated as an integer, an attacker could supply 1; cat /etc/shadow, leading to command injection or unauthorized file access if the application later uses this path in a system call. The CockroachDB interaction might occur when the application logs the file access or stores metadata, where the tainted fileId is persisted and later used in a query without sanitization.
These examples highlight that the combination of Hapi’s routing flexibility and CockroachDB’s SQL interface creates a surface where improper input handling can lead to path traversal or injection issues. The database does not inherently prevent these logical flaws; it executes whatever commands it receives. Therefore, the onus is on the developer to validate, sanitize, and parameterize all external inputs before they influence database queries or any file system–adjacent operations.
Cockroachdb-Specific Remediation in Hapi — concrete code fixes
Remediation centers on strict input validation, parameterized queries, and avoiding any direct string assembly for SQL. For Hapi routes interacting with CockroachDB, always use prepared statements or query builders that enforce parameterization. Below are concrete, secure code examples that address the scenarios described earlier.
Secure route with parameterized query:
const Hapi = require('@hapi/hapi');
const { Pool } = require('pg');
const init = async () => {
const server = Hapi.server({ port: 3000, host: 'localhost' });
const pool = new Pool({ connectionString: 'postgresql://user:pass@cockroachdb-host:26257/mydb' });
server.route({
method: 'GET',
path: '/user/{username}',
handler: async (request, h) => {
const { username } = request.params;
// Validate username format (alphanumeric and underscores only)
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
throw Boom.badRequest('Invalid username format');
}
const result = await pool.query('SELECT * FROM users WHERE username = $1', [username]);
return result.rows[0];
}
});
await server.start();
};
init();
In this corrected version, the route validates the username against a strict regex before using it. The SQL query uses a parameterized placeholder ($1) provided by the pg driver, which ensures that the input is treated strictly as data, not executable SQL. This neutralizes injection and path traversal risks.
Secure file identifier handling with integer casting and validation:
server.route({
method: 'GET',
path: '/files/{fileId}',
handler: async (request, h) => {
let fileId = parseInt(request.params.fileId, 10);
if (isNaN(fileId) || fileId <= 0) {
throw Boom.badRequest('Invalid file identifier');
}
const baseDir = '/data/files';
const resolvedPath = require('path').join(baseDir, fileId.toString());
// Additional check to ensure resolved path stays within baseDir
if (!resolvedPath.startsWith(baseDir)) {
throw Boom.forbidden('Access denied');
}
// Proceed with database lookup using parameterized query
const result = await pool.query('SELECT * FROM files WHERE id = $1', [fileId]);
return result.rows[0];
}
});
This example demonstrates strict integer validation for the fileId. The path is constructed using Node’s path.join to avoid manual string concatenation, and a sanity check ensures the resolved path does not escape the intended base directory. The database query remains parameterized, preserving separation between code and data.
Using an ORM or query builder for complex queries:
const { Pool } = require('pg');
const { db } = require('objection'); // Example with objection.js
const fetchUserSafe = async (username) => {
return await db.knex()('users').where('username', username).first();
};
// In Hapi handler:
handler: async (request, h) => {
const { username } = request.params;
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
throw Boom.badRequest('Invalid username');
}
return await fetchUserSafe(username);
}
Using an abstraction layer like Objection.js further reduces the chance of accidental string concatenation. The library enforces parameterized queries internally. These patterns collectively mitigate path traversal and injection risks when combining Hapi and CockroachDB.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |