medium

CVE-2026-55410

npm · @nocobase/plugin-backups

Summary

NocoBase backup restore schema name allows command injection

Severity
medium
CVSS
6.7
EPSS
0.7% (p54)
CWE
CWE-78
Also known as
GHSA-p853-83gj-wjj3
Published
2026-08-20
Updated
2026-08-20

Advisory details

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:

Source-to-sink path:

A schema value such as safe; touch /tmp/nocobase-cve-marker # produces a restore command of this form:

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:

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:

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:

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 argumen

References

Related advisories

Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.

Check my repo

Summarize with AI

ChatGPTClaudePerplexity

Sources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.