The Fourth Path Was the MCP Interface: nginx-ui’s Four CVEs
NVD published four nginx-ui CVEs at 21:17Z yesterday — CVE-2026-84317 (8.8, CWE-20 + CWE-693), CVE-2026-84318 (8.8, CWE-693), CVE-2026-84319 (8.3, CWE-693) and CVE-2026-84360 (8.1, CWE-288) — all vulnStatus: Received, all CVSS 3.1 only, all reading “prior to 2.4.1” and “fixed in version 2.4.1.” The assigner is GitHub as CNA. Three days after the ten-advisory October batch, this second wave tells a tighter story than the first: every one of the four describes a security control that the REST API enforced and the MCP interface did not. The model-facing path was the unpatched one.
The sharpest record is 84317, because it counts the paths. internal/config/validation.go (ValidateConfigFile) blocks dangerous nginx directives — the js_* family, perl_*, load_module of restricted dynamic modules, and user root — and it was enforced on three of the four config-write paths: api/config/add.go, api/config/modify.go and mcp/config/config_modify.go. The fourth path, the MCP tool nginx_config_add (mcp/config/config_add.go), did not call it: it wrote caller-supplied content straight to disk and reloaded nginx. We verified this first-hand at tag v2.4.0: handleNginxConfigAdd goes from path resolution to os.WriteFile to nginx.Control(nginx.Reload) with no validation call, while the sibling modify handler and the REST add handler both gate on ValidateConfigFile. At v2.4.1 the fix is exactly five added lines — the ValidateConfigFile(path, content) call, returning early on error — matching the commit diff line for line. CVE-2026-84318 describes the same gap from the attacker’s side (affected from 2.0.0-rc.6): any blocked directive — perl, js_import, load_module, user root — writable post-authentication, which on a server with the Perl, njs or Lua module loaded is remote code execution, including in the official Docker image.
The restricted directives, and the missing step-up
CVE-2026-84319 narrows the lens to privilege separation between callers: the HTTP API rejects restricted directives, but the MCP interface let a secondary authenticated user write and apply equivalent content — load ngx_http_js_module.so, import attacker-controlled JavaScript, reload, and execute NJS in the worker context. CVE-2026-84360 widens it to session strength: the HTTP mutation routes required RequireSecureSession, but /mcp and /mcp_message required only ordinary authentication, so an OTP-enabled user with a normal JWT but no valid X-Secure-Session-ID could create or modify nginx configuration through MCP, bypassing the intended step-up control. The July fix commit carries both halves: mcp/router.go gains requireSecureSessionForSensitiveMCPTools() on both routes — probing the request body for seven named sensitive tools (nginx_config_add, nginx_config_modify, nginx_config_enable, mkdir, rename, reload_nginx, restart_nginx) and demanding a secure session only for those — while RequireSecureSession itself is extended so passkey holders are treated like OTP holders. Selective step-up on the machine-readable interface, added in the same commit as the validation gate.
The disclosure shape is the now-familiar one, and the dates are exact. Fix commit 23d1934 (fix: harden security advisory paths, by 0xJacky) landed 3 July 2026 at 13:12Z; tag v2.4.1 shipped the same day at 13:48Z; the CVEs published 12 October at 21:17Z — a 101-day silent-fix window, inside which the project went on shipping all the way to v2.8.4 (7 October). Anyone running 2.4.1 or later has had the fix for months without knowing what it was; anyone pinning an older 2.x for stability has been exposed to MCP-reachable nginx RCE with no advisory telling them so. This is the same silent-fix-then-disclose shape as the deepseek-harness VM escape, except here the fix sat in a tagged release the whole time rather than on a branch.
What to do, in version numbers that exist
- Run 2.4.1 or later (current is 2.8.4). Unlike the October batch’s pseudo-version maze, these four name a real floor: 2.4.1. Upgrade past it.
- If you expose nginx-ui’s MCP endpoint to agents, audit what was written through it. Pre-2.4.1, any authenticated MCP caller — including an agent induced via prompt injection, as the 84317 description explicitly notes — could plant
load_module/js_importcontent and trigger a reload. Review config files and reload history, not just access logs. - Re-check step-up assumptions for OTP/passkey users. Pre-2.4.1, the MCP routes did not enforce the secure-session control your HTTP threat model assumed. Any config change attributed to an OTP user without a step-up event deserves a second look.
- Count your write paths, all of them. The editorial lesson of this batch generalises past nginx-ui: every interface added after the security control — MCP today, the next protocol tomorrow — re-opens the control unless it is called from the shared enforcement point. Three of four is zero of four against an attacker who can pick the interface.
Verification note. All four CVE identifiers, NVD publication timestamps (12 October 2026, 21:17:22Z), CVSS 3.1 scores and vectors, CWE assignments, affected ranges and vulnStatus: Received were read first-hand from the NVD 2.0 API. Assigner GitHub_M and CVE publication date 20:20:18Z were read first-hand from the CVE Program API. The vulnerable/fixed code comparison (mcp/config/config_add.go at v2.4.0 vs v2.4.1), the sibling-path checks (config_modify.go:64, api/config/add.go:46), the blocked-directive list in internal/config/validation.go, and the router/middleware diffs were read first-hand from raw.githubusercontent.com and the GitHub commits API for 23d1934. Release dates (v2.4.1 3 July, v2.8.4 7 October) and repository metadata (11,605 stars, 899 forks) from the GitHub API. We did not install nginx-ui, write any config through it, or send requests to any deployment. The write-path-counting analysis is our editorial reading. We have no evidence of exploitation and imply none.
Sources:
- NVD — CVE-2026-84317, MCP
nginx_config_addskipsValidateConfigFile(8.8) - NVD — CVE-2026-84318, MCP config write without validation from 2.0.0-rc.6 (8.8)
- NVD — CVE-2026-84319, MCP allows restricted NJS directives to secondary users (8.3)
- NVD — CVE-2026-84360, MCP routes skip
RequireSecureSessionstep-up (8.1) - 0xJacky/nginx-ui commit
23d1934— fix: harden security advisory paths (3 July 2026)