Dangling Dns in Feathersjs with Cockroachdb
Dangling Dns in Feathersjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
A dangling DNS record in a Feathersjs application that uses Cockroachdb can expose an API to unintended data access or redirection. Feathersjs services often resolve database hostnames at startup or runtime, and if a DNS entry is removed or reconfigured without corresponding service updates, the service may continue to reference a stale hostname. When the application attempts to open a Cockroachdb connection using that stale DNS name, it might resolve to an external or attacker-controlled endpoint, or fail in a way that exposes sensitive information through unhandled errors.
During a black-box scan, middleBrick tests unauthenticated attack surfaces across multiple security checks, including Input Validation, SSRF, and Property Authorization. If a Feathersjs service dynamically constructs connection strings using user-supplied input to build hostnames or paths, and that input is not strictly validated, an attacker can supply a hostname that resolves via DNS to an internal Cockroachdb listener or an unintended external service. This can lead to SSRF-like behavior, data exposure, or authentication bypass depending on how the service handles resolution failures and retries. The presence of Cockroachdb-specific connection parameters—such as secure cluster endpoints with TLS certificates—does not prevent misuse if the DNS name itself is untrusted or user-influenced.
For example, a Feathersjs hook that builds a Cockroachdb connection URL from a tenant identifier without strict allowlisting can be abused if an attacker registers a hostname that points to a different Cockroachdb cluster. The service may inadvertently connect to the attacker’s cluster and leak connection metadata or error messages that disclose schema or version information. middleBrick’s LLM/AI Security checks do not apply here because this is not an LLM endpoint, but the scan’s checks for Input Validation, SSRF, and Data Exposure will highlight risky patterns where DNS resolution is coupled with runtime configuration.
In practice, you should ensure that DNS names used for Cockroachdb connections are static, verified, and rotated only through controlled infrastructure changes. Avoid concatenating user input into hostnames or paths that resolve externally. If your Feathersjs service must accept a hostname, validate it against a strict allowlist of domains and enforce network-level restrictions so that only intended Cockroachdb endpoints are reachable. These practices reduce the risk of a dangling DNS record leading to data exposure or redirection in production.
Cockroachdb-Specific Remediation in Feathersjs — concrete code fixes
To remediate DNS-related risks when using Cockroachdb with Feathersjs, validate and constrain all hostnames used in connection strings, and avoid dynamic hostname assembly from untrusted input. Use environment variables for static hostnames and enforce allowlists for any configurable endpoints. Below are concrete, realistic examples for a Feathersjs service that connects to Cockroachdb using the pg client, which is compatible with Cockroachdb’s PostgreSQL wire protocol.
Static hostname configuration with environment validation
Define a fixed hostname in your deployment environment and validate it at startup. This prevents runtime changes that could point to unintended endpoints.
// src/app.js
const feathers = require('@feathersjs/feathers');
const { Pool } = require('pg');
const ALLOWED_HOSTS = new Set([
'cockroachdb-prod.example.com',
'cockroachdb-staging.example.com'
]);
const dbHost = process.env.COCKROACHDB_HOST;
if (!dbHost || !ALLOWED_HOSTS.has(dbHost)) {
throw new Error('Invalid or missing COCKROACHDB_HOST');
}
const pool = new Pool({
host: dbHost,
port: 26257,
database: process.env.COCKROACHDB_DB,
user: process.env.COCKROACHDB_USER,
password: process.env.COCKROACHDB_PASSWORD,
ssl: {
rejectUnauthorized: true
}
});
const app = feathers();
app.configure({
pool,
Model: require('feathers-objection').Model,
name: 'widgets'
});
module.exports = app;
Strict input validation for tenant-aware routing
If you must route based on tenant identifiers, map them to predefined connection configurations rather than building hostnames directly.
// src/services/widgets/widgets.class.js
const { Forbidden } = require('@feathersjs/errors');
const TENANT_CONFIGS = {
tenantA: {
host: 'cockroachdb-tenant-a.example.com',
database: 'tenant_a_db'
},
tenantB: {
host: 'cockroachdb-tenant-b.example.com',
database: 'tenant_b_db'
}
};
class WidgetsService {
constructor(options) {
this.options = options;
}
async find(params) {
const tenantId = params.query && params.query.tenant;
const config = TENANT_CONFIGS[tenantId];
if (!config) {
throw new Forbidden('Invalid tenant');
}
const { Pool } = require('pg');
const pool = new Pool({
host: config.host,
port: 26257,
database: config.database,
user: process.env.COCKROACHDB_USER,
password: process.env.COCKROACHDB_PASSWORD,
ssl: {
rejectUnauthorized: true
}
});
const client = await pool.connect();
try {
const res = await client.query('SELECT * FROM widgets LIMIT $1', [params.query.limit || 10]);
return res.rows;
} finally {
client.release();
await pool.end();
}
}
}
module.exports = function () {
const app = feathers();
app.use('/widgets', new WidgetsService({
paginate: { default: 10, max: 25 }
}));
return app;
};
Connection string sanitization for user-supplied parameters
If you accept identifiers that map to connection parameters, validate and sanitize them strictly. Do not directly interpolate values into connection strings.
// src/hooks/validate-tenant.js
const ALLOWED_TENANTS = ['tenantA', 'tenantB', 'tenantC'];
module.exports = function () {
return async context => {
const tenant = context.params.query && context.params.query.tenant;
if (tenant && !ALLOWED_TENANTS.includes(tenant)) {
throw new (require('@feathersjs/errors').BadRequest)('Invalid tenant identifier');
}
// Optionally inject sanitized config into context for later use
context.params.dbConfig = tenant ? getTenantConfig(tenant) : getDefaultConfig();
};
};
function getTenantConfig(tenant) {
const map = {
tenantA: { database: 'tenant_a_db' },
tenantB: { database: 'tenant_b_db' },
tenantC: { database: 'tenant_c_db' }
};
return map[tenant];
}
function getDefaultConfig() {
return { database: 'default_db' };
}