psf-nginx-ui-check(8)
nginx-ui restricted-directive bypass (authenticated RCE): read-only checker (v2)
CVE-2026-84316, CVE-2026-84317, CVE-2026-84318, CVE-2026-84319
If this software is helpful, consider a donation to our 501c3 nonprofit:
https://privacysafe.foundation
nginx-ui is a web interface for managing nginx. It has a security control,
ValidateConfigFile, that refuses configuration containing directives that can run
code or raise privileges (load_module of the njs or Perl modules, js_import,
js_content, perl_*, user root). Four advisories published 12 October 2026 describe
ways around that control for a logged-in user. Where nginx loads the njs, Perl or
Lua module (the official Docker image does), the injected configuration runs code
in the nginx worker.
CVE-2026-84316 CVSS 8.8 The validator split the configuration on newlines and
looked only at the first token of each line. nginx separates
directives with ";", so a harmless directive first and a
restricted one after a semicolon on the same line passed.
CVE-2026-84317 CVSS 8.8 The MCP tool nginx_config_add wrote the caller's
CVE-2026-84318 CVSS 8.8 configuration to disk and reloaded nginx without calling
ValidateConfigFile at all. The REST paths do call it. Two IDs
describe this one gap, with different affected ranges.
CVE-2026-84319 CVSS 8.3 The MCP configuration interface let a secondary
authenticated user write and apply the restricted directives the
regular API refuses (load_module of the njs module, js_import),
which executes njs in the nginx worker.
Affected: nginx-ui below 2.4.1 (84318 from 2.0.0-rc.6).
Fixed: nginx-ui 2.4.1.
How this script reads an install. The nginx-ui binary has no version flag, and its
version endpoint needs a login, so a read-only check cannot ask it. Instead:
0. The official container image, uozi/nginx-ui, is judged by its tag when the tag names a
version (a tag such as latest does not, and is a warning). With no binary and no image
on the host, a version you supply in NGINX_UI_VERSION is judged on its own.
1. NGINX_UI_VERSION (you set it) or the package version, when there is one;
2. a string inside the binary. The 2.4.1 validator adds the restricted names
lua_package_cpath and lua_package_path, which 2.4.0 does not contain. A binary
without them is the older validator (strong finding). A binary with them is
consistent with 2.4.1 or later; it is a string match, so confirm the version
shown in the web interface.
This script is read-only.
Usage:
./nginx-ui-check.sh [command]
check (default) find nginx-ui, read its version and the binary signature, report.
Exit 0 = nothing exposed, 1 = exposed or unclear.
hunt read nginx configuration for the semicolon bypass shape and list
restricted directives present, for review. Exit 0 = nothing found,
1 = a strong lead. A clean hunt means none of the published indicators were found.
version print the script version.
Env: NGINX_UI_BIN path to the nginx-ui binary
NGINX_UI_VERSION the version, from the web interface, if you know it
NGINX_UI_CONF_DIRS space separated nginx configuration directories for hunt
(default /etc/nginx /etc/angie /usr/local/etc/nginx /usr/local/nginx/conf)
Containers: with no argument the check also looks in running docker and podman containers
(SCAN_CONTAINERS=1, the default; 0 skips them). A read-only probe runs the two signature tests
this check applies to the binary (lua_package_cpath and lua_package_path) inside the container
and copies only a small file recording the answer, so the binary itself is never copied. The
version is not read by running nginx-ui, so a container is judged by that signature alone. Paths
shown as .../docker_NAME/... are inside the container NAME.The Security Patches software and this documentation are dedicated to the public domain under CC0 1.0 Universal. Other pages on privacysafe.app/docs are CC BY-SA 4.0 unless the page says otherwise.