CVE-2026-55410: @nocobase/plugin backups NocoBase Backup Restore Schema Name Vulnerability

MODERATE
Advisory Identifier: GHSA-p853-83gj-wjj3
CVSS 6.7
CWE-78
Published
Advisory Summary

CVE-2026-55410 is a MODERATE (CVSS 6.7) nocobase backup restore schema name vulnerability (CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')) affecting @nocobase/plugin backups libraries in the npm ecosystem. The issue involves nocobase backup restore schema name allows command injection. Remediation is available by upgrading affected packages to version 2.1.19.

CVE-2026-55410 at a Glance

CVE Identifier CVE-2026-55410
GHSA Identifier GHSA-p853-83gj-wjj3
Severity MODERATE
CVSS v3.1 Base Score 6.7
CWE Classification CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
Affected Product @nocobase/plugin backups
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 6.7

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:L/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 Low (C:L)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-55410

To remediate this vulnerability, upgrade all affected @nocobase/plugin backups 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-backups fixed: 2.1.19
npm install @nocobase/plugin-backups@2.1.19
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-backups

How to Check if You Are Affected

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

npm ls @nocobase/plugin-backups

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

Note: The npm ls @nocobase/plugin-backups 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-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection'), where the product constructs all or part of an os command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended os command when it is sent to a downstream component. In @nocobase/plugin backups, 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 NocoBase `@nocobase/plugin-backups` 2.0.57 restores PostgreSQL backups by interpolating the backup metadata schema name into shell command strings that are executed with Node.js `child_process.exec()`. A backup-management user who can restore an uploaded PostgreSQL backup with forced schema restore can place shell metacharacters in `_metadata.json` under `database.schema`, causing arbitrary commands to execute as the NocoBase server process during restore. The vulnerable plugin is included in the default `@nocobase/preset-nocobase` package and is guarded by the backup-management ACL snippet (`backups:*` / `backup:*`). This is not unauthenticated; the attacker must have backup restore privileges or equivalent access to the restore API/CLI. ### Details Affected product evidence: - Ecosystem/package: npm package `@nocobase/plugin-backups` from `packages/plugins/@nocobase/plugin-backups/package.json`. - Tested vulnerable version: `2.0.57` (`packages/plugins/@nocobase/plugin-backups/package.json:1-16`). - Tested commit: `e03d267362b3426f484c28783020b4a2a08911e8`. - Default/common inclusion: `@nocobase/preset-nocobase` depends on and lists `@nocobase/plugin-backups` 2.0.57 as built in (`packages/presets/nocobase/package.json:22-24`, `packages/presets/nocobase/package.json:115-128`). - Affected range estimate: at least the tested `2.0.57` checkout. Earlier/later versions were not tested. - Patched version: unknown/not available in this local checkout. Source-to-sink path: - The plugin registers backup-management snippets for `backups:*` and `backup:*`, so the restore API is intended for roles granted backup-management permissions (`packages/plugins/@nocobase/plugin-backups/src/server/plugin.ts:51-59`). - The `backup` restore-upload action accepts request body/query `force` and passes it as `forceSchemaRestore` to `RestoreManager.restore()` (`packages/plugins/@nocobase/plugin-backups/src/server/resourcers/backup-cli.ts:40-42`, `packages/plugins/@nocobase/plugin-backups/src/server/resourcers/backup-cli.ts:200-211`). - `RestoreManager` decompresses the uploaded backup archive, reads `_metadata.json`, and parses attacker-controlled JSON metadata (`packages/plugins/@nocobase/plugin-backups/src/server/managers/restore.ts:203-215`, `packages/plugins/@nocobase/plugin-backups/src/server/managers/restore.ts:257-267`). - When `forceSchemaRestore` is true and the database dialect is PostgreSQL, the schema-mismatch check is skipped (`packages/plugins/@nocobase/plugin-backups/src/server/managers/restore.ts:270-300`). Existing tests confirm forced schema restore intentionally allows a metadata schema mismatch (`packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/restore.test.ts:336-356`) and that the API passes `forceSchemaRestore: true` when `force=true` is supplied (`packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/restore.test.ts:377-409`). - The parsed `metadata.database.schema` is passed into `this.#dbAdapter.restore(path.join(extractedDir, dbFile), metadata.database.schema)` (`packages/plugins/@nocobase/plugin-backups/src/server/managers/restore.ts:427-448`). - For PostgreSQL, if the backup schema differs from the target schema, `PostgresAdapter.restore()` assigns `srcSchema = schema || 'public'` and builds `pgRestoreCommand` using `-n ${srcSchema}` with no quoting or argument array (`packages/plugins/@nocobase/plugin-backups/src/server/adapters/database.ts:350-420`). - `#restoreSchema()` also interpolates `srcSchema` and `targetSchema` directly into SQL strings and then calls `run(pgRestoreCommand, ...)` (`packages/plugins/@nocobase/plugin-backups/src/server/adapters/database.ts:423-451`). - `run()` executes the assembled string through `child_process.exec()`, which invokes a shell (`packages/plugins/@nocobase/plugin-backups/src/server/adapters/database.ts:1-31`). A schema value such as `safe; touch /tmp/nocobase-cve-marker #` produces a restore command of this form: ```text pg_restore -U u -h localhost -p 5432 -n safe; touch /tmp/nocobase-cve-marker # -d db --clean --if-exists --no-owner -j 1 /tmp/backup-data ``` The semicolon terminates the intended `pg_restore` command and starts a new shell command. False-positive screening: - This report does not claim unauthenticated exploitation. The route is gated by backup-management permissions through the registered ACL snippet. - The older `backups.upload` resource path was reviewed and does not pass `forceSchemaRestore`; the directly confirmed force path is the `backup.restoreUpload` / backup-CLI API path (`packages/plugins/@nocobase/plugin-backups/src/server/resourcers/backups.ts:54-77`, `packages/plugins/@nocobase/plugin-backups/src/server/resourcers/backup-cli.ts:200-211`). - The schema mismatch check blocks mismatched metadata by default, but it is deliberately bypassed for PostgreSQL when the supported force option is true. - The command injection is in the shell command itself before any PostgreSQL connection or valid backup file is required; the safe PoC proves shell metacharacter execution locally without connecting to a database. - The finding is not based on existing reports or generated writeups. ### PoC The following local-only PoC renders the same vulnerable `pg_restore` command shape built by `PostgresAdapter.restore()` and executes it through Node.js `child_process.exec()`, the sink used by the plugin. It uses a harmless marker file under `/tmp`, does not contact external services, and cleans up after itself. From a clean checkout of the tested commit: ```bash cd nocobase rm -f /tmp/nocobase-cve-marker node - <<'NODE' const { exec } = require('child_process'); const schemaFromBackupMetadata = 'safe; touch /tmp/nocobase-cve-marker #'; const command = `pg_restore -U u -h localhost -p 5432 -n ${schemaFromBackupMetadata} -d db --clean --if-exists --no-owner -j 1 /tmp/backup-data`; exec(command, () => { const fs = require('fs'); console.log(fs.existsSync('/tmp/nocobase-cve-marker') ? 'marker-created' : 'marker-missing'); fs.rmSync('/tmp/nocobase-cve-marker', { force: true }); }); NODE ``` Observed output in this environment: ```text marker-created ``` Expected vulnerable output: `marker-created`, proving the schema value starts a second shell command. Negative/control case: replace `schemaFromBackupMetadata` with `safe_schema`; the same harness should print `marker-missing` because no shell metacharacter starts the `touch` command. Maintainer-runnable application-level trigger: 1. Run NocoBase 2.0.57 with PostgreSQL and the built-in `@nocobase/plugin-backups` enabled. 2. Use a role granted the backup-management snippet/actions (`backups:*` / `backup:*`). 3. Create a NocoBase backup archive containing a `data` member and `_metadata.json` with matching dialect/table settings but `database.schema` set to `safe; touch /tmp/nocobase-cve-marker #`. 4. Restore the uploaded backup through the `backup` restore-upload path with `force=true` so PostgreSQL schema mismatch is allowed. 5. Vulnerable behavior: `/tmp/nocobase-cve-marker` exists on the server after restore begins, even if `pg_restore` or database connection later fails. 6. Cleanup: remove `/tmp/nocobase-cve-marker` and discard the test database/container. ### Impact A user with backup restore privileges can execute arbitrary shell commands as the NocoBase server OS user. In a typical server or container deployment, this can read application configuration and environment secrets, modify application files or database backups, run network clients from the server, and disrupt service availability. The required application privilege is high because backup restore is an administrative operation. However, backup-management permission is still an application-level role boundary; it should not imply arbitrary operating-system command execution. ### Suggested remediation Do not execute database tools through shell-interpreted command strings. Use `spawn()` or `execFile()` with an argument array for `pg_restore`, `psql`, `pg_dump`, `mysql`, and related tools. Validate PostgreSQL schema identifiers from backup metadata against PostgreSQL identifier rules or quote them using the database driver's identifier-quoting facilities before using them in SQL. For this specific path: - Pass `pg_restore` arguments as an array, for example `['-U', username, '-h', host, '-p', String(port), '-n', srcSchema, '-d', database, ...]`. - Reject schema names containing shell metacharacters, quotes, whitespace, comments, or characters outside accepted PostgreSQL identifier syntax unless they are safely handled as identifiers. - Replace dynamic SQL string interpolation in `#restoreSchema()` with identifier-safe quoting (`format('%I', ...)` in PostgreSQL or equivalent server-side parameters) and string-literal escaping where literals are required. - Add regression tests that restore a backup whose metadata schema contains `; touch /tmp/should-not-exist #` and assert no marker file is created and the request is rejected.

CVE-2026-55410 Affected @nocobase/plugin backups Versions

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

References

Frequently Asked Questions

What is CVE-2026-55410?
CVE-2026-55410 is a security vulnerability in @nocobase/plugin backups involving NocoBase backup restore schema name allows command injection under CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
Which @nocobase/plugin backups versions are affected?
Affected versions include releases prior to 2.1.19 across affected packages: @nocobase/plugin-backups.
What version fixes CVE-2026-55410?
Upgrading to 2.1.19 (or the latest patched release for your respective branch) resolves the vulnerability.
Is @nocobase/plugin-backups affected?
Yes, @nocobase/plugin-backups is affected in releases prior to 2.1.19. Upgrade your dependency to 2.1.19 or later.
What is the CVSS score of CVE-2026-55410?
CVE-2026-55410 has a CVSS v3.1 base score of 6.7 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:H/A:H.
What CWE is associated with CVE-2026-55410?
CVE-2026-55410 is classified under CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.).
How can I check npm dependencies for CVE-2026-55410?
Run the dependency tree command: 'npm ls @nocobase/plugin-backups'. 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-p853-83gj-wjj3",
  "modified" : "2026-08-20T18:30:15Z",
  "published" : "2026-08-20T18:30:15Z",
  "aliases" : [ "CVE-2026-55410" ],
  "summary" : "NocoBase backup restore schema name allows command injection",
  "details" : "### Summary\nNocoBase `@nocobase/plugin-backups` 2.0.57 restores PostgreSQL backups by interpolating the backup metadata schema name into shell command strings that are executed with Node.js `child_process.exec()`. A backup-management user who can restore an uploaded PostgreSQL backup with forced schema restore can place shell metacharacters in `_metadata.json` under `database.schema`, causing arbitrary commands to execute as the NocoBase server process during restore.\n\nThe vulnerable plugin is included in the default `@nocobase/preset-nocobase` package and is guarded by the backup-management ACL snippet (`backups:*` / `backup:*`). This is not unauthenticated; the attacker must have backup restore privileges or equivalent access to the restore API/CLI.\n\n### Details\nAffected product evidence:\n- Ecosystem/package: npm package `@nocobase/plugin-backups` from `packages/plugins/@nocobase/plugin-backups/package.json`.\n- Tested vulnerable version: `2.0.57` (`packages/plugins/@nocobase/plugin-backups/package.json:1-16`).\n- Tested commit: `e03d267362b3426f484c28783020b4a2a08911e8`.\n- Default/common inclusion: `@nocobase/preset-nocobase` depends on and lists `@nocobase/plugin-backups` 2.0.57 as built in (`packages/presets/nocobase/package.json:22-24`, `packages/presets/nocobase/package.json:115-128`).\n- Affected range estimate: at least the tested `2.0.57` checkout. Earlier/later versions were not tested.\n- Patched version: unknown/not available in this local checkout.\n\nSource-to-sink path:\n- The plugin registers backup-management snippets for `backups:*` and `backup:*`, so the restore API is intended for roles granted backup-management permissions (`packages/plugins/@nocobase/plugin-backups/src/server/plugin.ts:51-59`).\n- The `backup` restore-upload action accepts request body/query `force` and passes it as `forceSchemaRestore` to `RestoreManager.restore()` (`packages/plugins/@nocobase/plugin-backups/src/server/resourcers/backup-cli.ts:40-42`, `packages/plugins/@nocobase/plugin-backups/src/server/resourcers/backup-cli.ts:200-211`).\n- `RestoreManager` decompresses the uploaded backup archive, reads `_metadata.json`, and parses attacker-controlled JSON metadata (`packages/plugins/@nocobase/plugin-backups/src/server/managers/restore.ts:203-215`, `packages/plugins/@nocobase/plugin-backups/src/server/managers/restore.ts:257-267`).\n- When `forceSchemaRestore` is true and the database dialect is PostgreSQL, the schema-mismatch check is skipped (`packages/plugins/@nocobase/plugin-backups/src/server/managers/restore.ts:270-300`). Existing tests confirm forced schema restore intentionally allows a metadata schema mismatch (`packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/restore.test.ts:336-356`) and that the API passes `forceSchemaRestore: true` when `force=true` is supplied (`packages/plugins/@nocobase/plugin-backups/src/server/__tests__/managers/restore.test.ts:377-409`).\n- The parsed `metadata.database.schema` is passed into `this.#dbAdapter.restore(path.join(extractedDir, dbFile), metadata.database.schema)` (`packages/plugins/@nocobase/plugin-backups/src/server/managers/restore.ts:427-448`).\n- For PostgreSQL, if the backup schema differs from the target schema, `PostgresAdapter.restore()` assigns `srcSchema = schema || 'public'` and builds `pgRestoreCommand` using `-n ${srcSchema}` with no quoting or argument array (`packages/plugins/@nocobase/plugin-backups/src/server/adapters/database.ts:350-420`).\n- `#restoreSchema()` also interpolates `srcSchema` and `targetSchema` directly into SQL strings and then calls `run(pgRestoreCommand, ...)` (`packages/plugins/@nocobase/plugin-backups/src/server/adapters/database.ts:423-451`).\n- `run()` executes the assembled string through `child_process.exec()`, which invokes a shell (`packages/plugins/@nocobase/plugin-backups/src/server/adapters/database.ts:1-31`).\n\nA schema value such as `safe; touch /tmp/nocobase-cve-marker #` produces a restore command of this form:\n\n```text\npg_restore -U u -h localhost -p 5432 -n safe; touch /tmp/nocobase-cve-marker # -d db --clean --if-exists --no-owner -j 1 /tmp/backup-data\n```\n\nThe semicolon terminates the intended `pg_restore` command and starts a new shell command.\n\nFalse-positive screening:\n- This report does not claim unauthenticated exploitation. The route is gated by backup-management permissions through the registered ACL snippet.\n- The older `backups.upload` resource path was reviewed and does not pass `forceSchemaRestore`; the directly confirmed force path is the `backup.restoreUpload` / backup-CLI API path (`packages/plugins/@nocobase/plugin-backups/src/server/resourcers/backups.ts:54-77`, `packages/plugins/@nocobase/plugin-backups/src/server/resourcers/backup-cli.ts:200-211`).\n- The schema mismatch check blocks mismatched metadata by default, but it is deliberately bypassed for PostgreSQL when the supported force option is true.\n- The command injection is in the shell command itself before any PostgreSQL connection or valid backup file is required; the safe PoC proves shell metacharacter execution locally without connecting to a database.\n- The finding is not based on existing reports or generated writeups.\n\n### PoC\nThe following local-only PoC renders the same vulnerable `pg_restore` command shape built by `PostgresAdapter.restore()` and executes it through Node.js `child_process.exec()`, the sink used by the plugin. It uses a harmless marker file under `/tmp`, does not contact external services, and cleans up after itself.\n\nFrom a clean checkout of the tested commit:\n\n```bash\ncd nocobase\nrm -f /tmp/nocobase-cve-marker\nnode - <<'NODE'\nconst { exec } = require('child_process');\n\nconst schemaFromBackupMetadata = 'safe; touch /tmp/nocobase-cve-marker #';\nconst command = `pg_restore -U u -h localhost -p 5432 -n ${schemaFromBackupMetadata} -d db --clean --if-exists --no-owner -j 1 /tmp/backup-data`;\n\nexec(command, () => {\n  const fs = require('fs');\n  console.log(fs.existsSync('/tmp/nocobase-cve-marker') ? 'marker-created' : 'marker-missing');\n  fs.rmSync('/tmp/nocobase-cve-marker', { force: true });\n});\nNODE\n```\n\nObserved output in this environment:\n\n```text\nmarker-created\n```\n\nExpected vulnerable output: `marker-created`, proving the schema value starts a second shell command.\n\nNegative/control case: replace `schemaFromBackupMetadata` with `safe_schema`; the same harness should print `marker-missing` because no shell metacharacter starts the `touch` command.\n\nMaintainer-runnable application-level trigger:\n1. Run NocoBase 2.0.57 with PostgreSQL and the built-in `@nocobase/plugin-backups` enabled.\n2. Use a role granted the backup-management snippet/actions (`backups:*` / `backup:*`).\n3. Create a NocoBase backup archive containing a `data` member and `_metadata.json` with matching dialect/table settings but `database.schema` set to `safe; touch /tmp/nocobase-cve-marker #`.\n4. Restore the uploaded backup through the `backup` restore-upload path with `force=true` so PostgreSQL schema mismatch is allowed.\n5. Vulnerable behavior: `/tmp/nocobase-cve-marker` exists on the server after restore begins, even if `pg_restore` or database connection later fails.\n6. Cleanup: remove `/tmp/nocobase-cve-marker` and discard the test database/container.\n\n### Impact\nA user with backup restore privileges can execute arbitrary shell commands as the NocoBase server OS user. In a typical server or container deployment, this can read application configuration and environment secrets, modify application files or database backups, run network clients from the server, and disrupt service availability.\n\nThe required application privilege is high because backup restore is an administrative operation. However, backup-management permission is still an application-level role boundary; it should not imply arbitrary operating-system command execution.\n\n### Suggested remediation\nDo not execute database tools through shell-interpreted command strings. Use `spawn()` or `execFile()` with an argument array for `pg_restore`, `psql`, `pg_dump`, `mysql`, and related tools. Validate PostgreSQL schema identifiers from backup metadata against PostgreSQL identifier rules or quote them using the database driver's identifier-quoting facilities before using them in SQL.\n\nFor this specific path:\n- Pass `pg_restore` arguments as an array, for example `['-U', username, '-h', host, '-p', String(port), '-n', srcSchema, '-d', database, ...]`.\n- Reject schema names containing shell metacharacters, quotes, whitespace, comments, or characters outside accepted PostgreSQL identifier syntax unless they are safely handled as identifiers.\n- Replace dynamic SQL string interpolation in `#restoreSchema()` with identifier-safe quoting (`format('%I', ...)` in PostgreSQL or equivalent server-side parameters) and string-literal escaping where literals are required.\n- Add regression tests that restore a backup whose metadata schema contains `; touch /tmp/should-not-exist #` and assert no marker file is created and the request is rejected.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@nocobase/plugin-backups"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.1.19"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/nocobase/nocobase/security/advisories/GHSA-p853-83gj-wjj3"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-55410"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nocobase/nocobase/commit/0e1aba1b7b112ffc841588963f7343c00edd9806"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/nocobase/nocobase"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nocobase/nocobase/releases/tag/v2.1.19"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-78" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-20T18:30:15Z",
    "nvd_published_at" : "2026-07-15T21:16:55Z"
  }
}