Injection

Injection in Node.js (Express) Guide [CVE-2026-40887] [CVE-2026-40887]

[Updated June 2026] Updated CVE-2026-40887

Overview

CVE-2026-40887 describes an unauthenticated SQL injection in Vendure's Shop API. In vulnerable versions, a user-controlled query string parameter is interpolated directly into a raw SQL expression, allowing an attacker to run arbitrary SQL against the database. This affects all supported backends (PostgreSQL, MySQL/MariaDB, SQLite). The Admin API is also affected, but exploitation there requires authentication. Patch versions exist (2.3.4, 3.5.7, 3.6.2), and Vendure also provided a hotfix that validates boundary inputs to block injection payloads before they reach queries. In Node.js (Express) projects, this class of vulnerability manifests when developers build SQL by concatenating user input, creating an attack surface that can be exploited through crafted query string parameters. Implementing parameterized queries and strict input validation is essential to mitigate these risks. These patterns and the referenced CVEs demonstrate the need for defense in depth across API boundaries and data access layers.

Affected Versions

Vendure Shop Admin API vulnerable in 1.7.4 and earlier; 2.3.3 and earlier; 3.5.6 and earlier; 3.6.1 and earlier. Patched in 2.3.4, 3.5.7, and 3.6.2.

Code Fix Example

Node.js (Express) API Security Remediation
Vulnerable and fixed Node.js (Express) code using PostgreSQL (pg) as example

// Run with: node app.js
const express = require('express');
const { Pool } = require('pg');
const app = express();
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

// Vulnerable pattern: string interpolation leads to SQL injection
app.get('/search-vulnerable', async (req, res) => {
  const name = req.query.name;
  // DO NOT DO THIS: vulnerable to SQL injection via name parameter
  const sql = "SELECT id, email FROM users WHERE name = '" + name + "'";
  try {
    const result = await pool.query(sql);
    res.json(result.rows);
  } catch (err) {
    console.error(err);
    res.status(500).send('Error');
  }
});

// Fixed pattern: use parameterized queries and input validation
app.get('/search-safe', async (req, res) => {
  const name = req.query.name;
  // Basic input validation (defense in depth). Adjust pattern to your domain.
  if (typeof name !== 'string' || !/^[\\w\\s-]+$/.test(name)) {
    return res.status(400).send('Invalid input');
  }
  // Use parameterized query with placeholder to prevent injection
  const sql = 'SELECT id, email FROM users WHERE name = $1';
  try {
    const result = await pool.query(sql, [name]);
    res.json(result.rows);
  } catch (err) {
    console.error(err);
    res.status(500).send('Error');
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

CVE References

Choose which optional cookies to allow. You can change this any time.