Apr 23, 2026·7 min read·3 visits
A path traversal flaw in Evolver allows attackers to write arbitrary files to the filesystem by manipulating the `--out` flag in the `fetch` command. Upgrading to version 1.69.3 patches the vulnerability.
The @evomap/evolver npm package prior to version 1.69.3 contains a critical path traversal vulnerability in its `fetch` command. Unsanitized input passed to the `--out` command-line flag allows an attacker to escape the intended directory structure and write arbitrary files to any location writable by the Node.js process.
The @evomap/evolver package provides a command-line utility for managing and downloading software components known as "skills". The fetch command within this utility accepts a --out flag, which designates the target directory for downloaded files. This component fails to enforce proper boundary checks on the directory path specified by the user.
The vulnerability is classified as Improper Limitation of a Pathname to a Restricted Directory (CWE-22) and External Control of File Name or Path (CWE-73). By supplying directory traversal sequences such as ../ within the --out flag payload, an attacker can manipulate the path resolution logic. This allows the application to resolve the output directory to an arbitrary location outside the intended application workspace.
Arbitrary File Write vulnerabilities directly compromise system integrity. The application relies on the underlying filesystem permissions of the executing Node.js process. If the application operates with elevated privileges, the attacker gains the ability to overwrite critical system binaries or configuration files.
The attack vector is network-adjacent in typical deployment scenarios. While Evolver is a command-line tool, it is frequently invoked by continuous integration systems, deployment pipelines, or backend job runners where user-supplied input influences the command-line arguments. This execution model exposes the CLI flag parsing logic to external manipulation.
The vulnerability originates in the argument parsing and directory resolution logic found in index.js. The application extracts the value of the --out flag using a direct string slice operation, bypassing any form of sanitization or validation. This raw string is then assigned to the outDir variable.
The application implements a defensive mechanism for the default directory path using a regular expression that removes alphanumeric anomalies. This sanitization step creates a safeId variable that ensures the default path remains within the bounds of the skills directory. The --out flag logic omits this crucial sanitization step entirely.
Once the outDir variable is populated with the unsanitized user input, the application invokes fs.existsSync and fs.mkdirSync. The recursive: true option in the directory creation call ensures that all parent directories specified in the traversal sequence are created if they do not exist. This creates a persistent change to the filesystem structure before any file data is written.
Subsequent file system operations write the downloaded "skill" payload directly into the resolved outDir. Because Node.js core filesystem modules resolve traversal sequences natively, the payload is deposited at the exact location specified by the attacker, limited only by the operating system permissions of the active process.
The vulnerable code path resides within index.js between lines 751 and 768. The application isolates the --out argument by checking for the --out= prefix and returning the remainder of the string. The resulting value dictates the application's file writing behavior.
// index.js:751-768 (Vulnerable Implementation)
const outFlag = args.find(a => typeof a === 'string' && a.startsWith('--out='));
const safeId = String(data.skill_id || skillId).replace(/[^a-zA-Z0-9_\-\.]/g, '_');
// VULNERABLE: No path validation on user input
const outDir = outFlag
? outFlag.slice('--out='.length) // User-controlled path directly assigned
: path.join('.', 'skills', safeId);
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });The fundamental error is the divergence in trust applied to the two execution paths. The ternary operator assigns either the unvalidated outFlag.slice output or the heavily sanitized safeId output to the same sensitive variable. A secure implementation must route all external input through a uniform validation chokepoint.
To remediate this bug class, the application must resolve the absolute path of the user-provided directory and verify that it strictly begins with the absolute path of the intended base directory. Node.js provides the path.resolve and path.normalize utilities for this purpose.
// Conceptual Secure Implementation
const baseDir = path.resolve(path.join('.', 'skills'));
let outDir = path.join(baseDir, safeId);
if (outFlag) {
const userDir = outFlag.slice('--out='.length);
const resolvedUserDir = path.resolve(baseDir, userDir);
// Verify the resolved path stays within the base directory boundaries
if (!resolvedUserDir.startsWith(baseDir + path.sep)) {
throw new Error('Path traversal detected');
}
outDir = resolvedUserDir;
}Exploiting this vulnerability requires control over the arguments passed to the evolver fetch command. The attacker appends the --out= flag followed by a relative path containing multiple ../ sequences to traverse upwards to the filesystem root. The attacker then specifies the target directory where the payload should be deployed.
The payload itself is delivered via the primary function of the fetch command, which is designed to download resources. The attacker provides a URL or identifier for a malicious skill. When the application executes the fetch routine, it retrieves the attacker's payload and writes it to the location defined by the traversed path.
The following proof-of-concept script simulates the vulnerable argument parsing logic and demonstrates the path resolution failure. The script executes the vulnerable logic and verifies that the output directory escapes the intended restricted environment.
// test-file-write.js
const fs = require('fs');
const path = require('path');
function vulnerableFetchSkill(outFlag) {
const outDir = outFlag
? outFlag.slice('--out='.length)
: path.join('.', 'skills', 'default');
return { outDir, targetFile: path.join(outDir, 'skill.js') };
}
// Exploit Case: Path traversal
const result = vulnerableFetchSkill('--out=../../../tmp/evolver-test');
try {
if (!fs.existsSync(result.outDir)) {
fs.mkdirSync(result.outDir, { recursive: true });
}
fs.writeFileSync(
path.join(result.outDir, 'poc.txt'),
'Path traversal successful.\n'
);
} catch (e) {
console.log('Error:', e.message);
}The direct impact of this vulnerability is arbitrary file write. An attacker can write new files or overwrite existing files on the host filesystem. The scope of the compromise depends entirely on the privileges of the system user executing the Evolver application.
Privilege escalation and remote code execution are standard secondary impacts of arbitrary file write flaws. Attackers achieve this by targeting user-specific initialization scripts such as ~/.bashrc, SSH authorized key files, or application source code. Overwriting these files ensures the attacker's payload is executed the next time the targeted resource is accessed or invoked.
In environments where Evolver runs with root privileges, the attacker gains full control over the operating system. Overwriting system-wide configuration files like /etc/crontab or installing malicious shared objects permits persistent, highly privileged execution.
The vulnerability carries a CVSS v3.1 base score of 8.1, reflecting a high severity issue. The attack vector is classified as Network (AV:N) due to the prevalence of CI/CD and automation pipelines where external input is passed directly to command-line utilities without validation. The confidentiality impact is rated None (C:N) as the flaw provides write access without explicitly granting read capabilities.
The primary remediation for GHSA-r466-rxw4-3j9j is updating the @evomap/evolver package. Version 1.69.3 introduces the necessary path validation logic to restrict the --out flag payload to the intended directory boundaries. Administrators must execute package manager updates to pull the patched version into their dependency trees.
For environments where immediate patching is impossible, operators should implement input validation wrappers. A wrapper script can inspect the arguments intended for the Evolver binary and reject any invocation where the --out flag contains traversal sequences like ../ or absolute path indicators like /.
Security monitoring systems should log the execution arguments of the Node.js processes running Evolver. Endpoint Detection and Response (EDR) platforms can trigger alerts on process creation events where command-line arguments match regular expressions targeting path traversal syntax. Additionally, monitoring for unexpected file creation events in sensitive directories provides a fallback detection mechanism.
Organizations should enforce the principle of least privilege for automated pipelines and service accounts. Running the Evolver application within an unprivileged container, a chroot jail, or under a restricted user account limits the blast radius of a successful arbitrary file write attack.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
@evomap/evolver EvoMap | < 1.69.3 | 1.69.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22, CWE-73 |
| Attack Vector | Network / Command Line Manipulation |
| CVSS Score | 8.1 (High) |
| Impact | Arbitrary File Write, Potential RCE |
| Exploit Status | PoC Available |
| Patched Version | 1.69.3 |
The software 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 software 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.