GitHub Security Advisory: GHSA-jxh8-jh77-xh6g
Package: @evomap/evolver (NPM)
Severity: HIGH (CVSS 8.1)
CWE: CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
Vulnerable versions: <= 1.70.0-beta.4
Patched in: 1.70.0-beta.5

Summary: @evomap/evolver's validator sandbox allowlist permits `npm`/`npx`, yielding RCE from Hub-delivered validation tasks via lifecycle scripts

Details:
## Summary

The validator-mode sandbox executor (`src/gep/validator/sandboxExecutor.js`) places `npm` and `npx` in its hard executable allowlist. Because `npm install <pkg>` and `npx -y -p <pkg> <bin>` execute arbitrary code by design (preinstall/install/postinstall lifecycle scripts and remote-package bin entries), and because validator nodes consume `validation_commands` strings from unsigned Hub responses with no per-response signature check, an attacker who controls or MITMs the Hub achieves automatic remote code execution on every validator node within one daemon poll (default 60s).

## Details

End-to-end chain:

1. `src/gep/validator/index.js:71-87` — `fetchValidationTasks()` POSTs to `<hub>/a2a/fetch` and reads `validation_tasks` from the JSON response. The outbound request is signed via `buildHubHeaders()`, but the Hub's response is parsed directly with `await res.json()` and no signature is verified on `data.payload`.

2. `src/gep/validator/index.js:98-108` — `validateOneTask()` extracts `task.validation_commands` (an array of attacker-controlled strings) and passes it straight to `runInSandbox(commands, {})`. No call to `policyCheck.isValidationCommandAllowed()` happens on this path. The author's own comment at `sandboxExecutor.js:41-42` acknowledges this gap: *"This closes the gap where validation_commands go straight from Hub to runInSandbox without passing through policyCheck.isValidationCommandAllowed()."*

3. `src/gep/validator/sandboxExecutor.js:172-218` — `runSingleCommand` calls `parseCommand(cmd)`, then checks `ALLOWED_EXECUTABLES.has(parsed.executable)`:

   ```js
   // sandboxExecutor.js:35
   const ALLOWED_EXECUTABLES = new Set(['node', 'npm', 'npx']);
   ```

   `parseCommand` only rejects shell metacharacters (`| & ; > < \` $`) and unbalanced quotes. A string like `npm install /tmp/evil-pkg --no-audit --no-fund` contains none of those and parses cleanly into `{ executable: 'npm', args: [...] }`.

4. `sandboxExecutor.js:54-66` — `assertNodeCommandSafe` is a no-op for non-`node` executables:

   ```js
   function assertNodeCommandSafe(parsed) {
     if (parsed.executable !== 'node') return;   // npm/npx skip every check
     ...
   }
   ```

   The `BLOCKED_NODE_FLAGS` set (`-e`, `-r`, `--loader`, etc.) therefore never gates `npm` or `npx` invocations.

5. `sandboxExecutor.js:213` — `spawn('npm', [...], { shell: false, cwd: sandboxDir, env })` runs `npm`. npm's documented behavior is to execute the package's `preinstall`, `install`, and `postinstall` scripts; `npx` downloads a remote package and executes its `bin` entry. Both yield arbitrary code execution in the validator process's UID/permissions.

6. `src/gep/validator/index.js:189` — the validator daemon polls every 60s by default (`EVOLVER_VALIDATOR_DAEMON_INTERVAL_MS`), and validator mode is **on by default** since v1.69.0 (`isValidatorEnabled()` returns `true` unless explicitly disabled, `index.js:25-34`).

The "sandbox" is nominal: it sets a fresh `cwd` and a stripp