HIGH symlink attackexpresscockroachdb

Symlink Attack in Express with Cockroachdb

Symlink Attack in Express with Cockroachdb — how this specific combination creates or exposes the vulnerability

A symlink attack in an Express service that uses CockroachDB can occur when file-system operations are influenced by attacker-controlled data before the application interacts with the database. Consider an Express endpoint that writes user-uploaded files to disk and records file metadata in CockroachDB. If the application uses user input to derive file paths without canonicalization or directory traversal checks, an attacker can craft a filename such as ../../../etc/passwd. On disk, this resolves outside the intended upload directory. The application then stores this user-supplied path in CockroachDB, for example in a file_records table.

Because CockroachDB is a distributed SQL database often used in production environments, a symlink stored in a table can be read by other services or administrative tooling that query the metadata. If the path is later used by a privileged backend process, the symlink can redirect reads or writes to sensitive locations. Although CockroachDB does not follow symlinks itself, the vulnerability arises from the interaction between Express file handling and the data stored in CockroachDB. An attacker who can influence the stored path can indirectly affect file resolution in other components that consume the database records.

Another variant involves temporary files created by Express middleware that are registered in CockroachDB for later processing. If these files are created insecurely (for example, using predictable names in shared directories) and then referenced in database rows, an attacker with filesystem access can replace the file with a symlink before the background worker reads it. The worker queries CockroachDB to locate the file, but the symlink leads to an unintended target. This combination of Express routes handling file metadata and CockroachDB as the authoritative store amplifies the impact because the database becomes a reliable pointer for malicious file references.

Real-world analogies include situations where an application stores user-controlled paths in database columns and later feeds them to shell commands or file readers. While CockroachDB itself does not introduce symlink behavior, its role as a durable store means that malicious paths persisted by Express can survive restarts and be used across distributed nodes. This persistence makes the attack surface broader compared to in-memory stores, as the symlink reference can be reused across sessions and services that rely on the same CockroachDB cluster.

Cockroachdb-Specific Remediation in Express — concrete code fixes

To mitigate symlink risks, ensure Express never trusts user input for filesystem paths and validate all paths against an allowlist. Use path canonicalization to eliminate .. and . segments before any database interaction. When storing file metadata in CockroachDB, persist only the canonicalized, application-controlled path and avoid storing user-supplied filenames directly.

Below are concrete Express code examples that interact with CockroachDB safely. The first example shows a secure upload flow that writes to a fixed directory and stores canonical metadata in CockroachDB using the cockroachdb Node.js client.

const express = require('express');
const fs = require('fs');
const path = require('path');
const { Client } = require('cockroachdb');

const app = express();
const uploadDir = path.resolve(__dirname, 'uploads');
const client = new Client({ connectionString: 'postgresql://user:pass@localhost:26257/db' });

app.post('/upload', express.raw({ type: '*/*' }), async (req, res) => {
  try {
    // Reject paths that attempt directory traversal
    const originalName = req.headers['x-file-name'] || 'unknown';
    const safeName = path.basename(originalName);
    const targetPath = path.join(uploadDir, safeName);

    // Ensure the resolved path is inside the upload directory
    if (!targetPath.startsWith(path.resolve(uploadDir))) {
      return res.status(400).send('Invalid file name');
    }

    fs.writeFileSync(targetPath, req.body);

    // Canonicalize before storing in CockroachDB
    const canonical = path.resolve(targetPath);
    await client.connect();
    await client.query(
      'INSERT INTO file_records (user_id, file_path, size) VALUES ($1, $2, $3)',
      [req.user.id, canonical, req.body.length]
    );

    res.status(201).send('Stored');
  } catch (err) {
    res.status(500).send('Server error');
  }
});

The second example demonstrates a worker that reads metadata from CockroachDB and processes files without following symlinks. By using fs.realpath and strict base-directory checks, the worker ensures that even if a malicious path somehow entered CockroachDB, it cannot escape the designated directory.

const fs = require('fs');
const path = require('path');
const { Client } = require('cockroachdb');

const uploadDir = path.resolve(__dirname, 'uploads');
const client = new Client({ connectionString: 'postgresql://user:pass@localhost:26257/db' });

async function processNextFile() {
  await client.connect();
  const res = await client.query('SELECT id, file_path FROM file_records WHERE processed = false LIMIT 1');
  if (res.rowCount === 0) return;

  const row = res.rows[0];
  const resolved = path.resolve(row.file_path);

  // Ensure the canonical path is still under the allowed directory
  if (!resolved.startsWith(path.resolve(uploadDir))) {
    await client.query('UPDATE file_records SET error = $1 WHERE id = $2', ['Path escape attempt', row.id]);
    return;
  }

  // Use fs.realpath to detect symlinks
  const real = fs.realpathSync(resolved);
  if (real !== resolved) {
    await client.query('UPDATE file_records SET error = $1 WHERE id = $2', ['Symlink detected', row.id]);
    return;
  }

  // Safe to process the file
  const data = fs.readFileSync(real);
  // ... processing logic ...

  await client.query('UPDATE file_records SET processed = true WHERE id = $2', [row.id]);
}

Additionally, configure CockroachDB with least-privilege access for the application user. Restrict the database user used by Express to only the necessary tables and operations, which limits the impact if an attacker manages to inject malicious paths. Combine this with runtime integrity checks on uploaded files and avoid storing executable content in directories served by Express.

Frequently Asked Questions

Can CockroachDB itself follow symlinks and cause data leaks?
No, CockroachDB does not follow filesystem symlinks. The risk comes from Express storing paths in CockroachDB that later get used by other processes that do follow symlinks. Mitigate by canonicalizing paths and validating them before persistence.
Does middleBrick detect symlink-related issues in Express APIs that interact with CockroachDB?
middleBrick scans the unauthenticated attack surface of Express endpoints and can identify insecure file handling patterns that may lead to symlink vulnerabilities. Findings include severity, remediation guidance, and mappings to frameworks such as OWASP API Top 10.