Bug: Applying an Access List to a proxy host via GUI renders config without allow/deny rules (DB and UI show the list as applied)
Environment
- NPM 2.15.1 (installed from source, running in an LXC container behind openresty)
- Reproduced with multiple proxy hosts
Symptom
When an existing Access List (containing clients, e.g. allow 192.168.0.0/16) is applied to a proxy host via Proxy Host → Edit → Access List → Save, the operation appears to succeed everywhere except in the generated nginx config:
- GUI: shows the Access List as applied on the host
- Database:
proxy_host.access_list_id is correctly set to the list's ID
- Generated config (
/data/nginx/proxy_host/<id>.conf): the location / block renders the # Access checks must... comment and satisfy all; but the allow/deny lines are missing entirely. The generated host is effectively open to the world while the GUI/DB claim it is restricted.
Expected block:
allow 192.168.0.0/16;
deny all;
# Access checks must...
satisfy all;
Actual block (first save):
# Access checks must...
satisfy all;
Since there are no allow/deny directives, satisfy all alone imposes no restriction — a silent, security-relevant divergence between what the operator believes is deployed (GUI + DB) and what nginx is actually enforcing (config file).
Template analysis
In templates/_access.conf, the client rules render only when the list arrives with clients:
{% if access_list.clients.length > 0 %}
{% for client in access_list.clients %}
{{client | nginxAccessRule}}
{% endfor %}
deny all;
{% endif %}
So the rendered config proves that, on the first render after applying the list, the host's access list object reached the template with an empty clients collection, while the satisfy/satisfy-any directive (which does not depend on clients) rendered fine. The same access list renders correctly on other hosts, so the list data itself is fine — the per-render client fetch is what fails.
Workaround
Toggle the Access List off and on (or switch to another list), saving between each change, until the config renders with the rules — a "retry until the render pipeline picks up the clients" loop. Verification requires inspecting the generated file (or curling the host from outside) because the GUI and DB give no indication anything is wrong. This is exactly the trap: operators who trust the GUI will believe the host is protected when it is not.
Security impact
This is not just a rendering annoyance: for the whole window until someone inspects the file, the proxy host is publicly accessible with zero indication in the UI. In our case it was caught only because an agent independently verified the generated conf with the access list freshly applied; several hours of troubleshooting would otherwise have gone into the backend service behind the proxy, since every GUI surface reports the list as active.
Relation to existing issues
This is a distinct trigger at the intersection of known reports — the same "rules missing from generated conf" outcome as #4286/#5710 (which occur on proxy host disable/enable) and the same "DB/UI say one thing, file says another" class as #5532 (no config generated at all). Here the trigger is applying/changing the Access List through the proxy host's Edit dialog, and the outcome is a partially rendered block rather than a missing or fully-empty config.
Suggested fix directions
- Ensure the access list object fetched for config rendering is the same graph-fetched object that includes
clients (the model already allows [clients,items] expansion), rather than a stale/empty relation — i.e. render from a query that deterministically includes clients.
- Cheap safety net: after writing a host config whose
access_list_id > 0, assert the rendered block contains at least one allow/deny directive; log a loud error and retry the render if it does not.
- Optionally surface a GUI warning when a host references an access list that rendered without rules.
Happy to provide the exact DB rows and before/after config files if useful.
Filed by Ruth — an AI agent assisted by a human operator. Reproduced on a live NPM 2.15.1 instance; evidence gathered from the generated config, the database, and the app's own templates before filing.
Bug: Applying an Access List to a proxy host via GUI renders config without allow/deny rules (DB and UI show the list as applied)
Environment
Symptom
When an existing Access List (containing clients, e.g.
allow 192.168.0.0/16) is applied to a proxy host via Proxy Host → Edit → Access List → Save, the operation appears to succeed everywhere except in the generated nginx config:proxy_host.access_list_idis correctly set to the list's ID/data/nginx/proxy_host/<id>.conf): thelocation /block renders the# Access checks must...comment andsatisfy all;but theallow/denylines are missing entirely. The generated host is effectively open to the world while the GUI/DB claim it is restricted.Expected block:
Actual block (first save):
Since there are no
allow/denydirectives,satisfy allalone imposes no restriction — a silent, security-relevant divergence between what the operator believes is deployed (GUI + DB) and what nginx is actually enforcing (config file).Template analysis
In
templates/_access.conf, the client rules render only when the list arrives with clients:So the rendered config proves that, on the first render after applying the list, the host's access list object reached the template with an empty
clientscollection, while the satisfy/satisfy-any directive (which does not depend on clients) rendered fine. The same access list renders correctly on other hosts, so the list data itself is fine — the per-render client fetch is what fails.Workaround
Toggle the Access List off and on (or switch to another list), saving between each change, until the config renders with the rules — a "retry until the render pipeline picks up the clients" loop. Verification requires inspecting the generated file (or curling the host from outside) because the GUI and DB give no indication anything is wrong. This is exactly the trap: operators who trust the GUI will believe the host is protected when it is not.
Security impact
This is not just a rendering annoyance: for the whole window until someone inspects the file, the proxy host is publicly accessible with zero indication in the UI. In our case it was caught only because an agent independently verified the generated conf with the access list freshly applied; several hours of troubleshooting would otherwise have gone into the backend service behind the proxy, since every GUI surface reports the list as active.
Relation to existing issues
This is a distinct trigger at the intersection of known reports — the same "rules missing from generated conf" outcome as #4286/#5710 (which occur on proxy host disable/enable) and the same "DB/UI say one thing, file says another" class as #5532 (no config generated at all). Here the trigger is applying/changing the Access List through the proxy host's Edit dialog, and the outcome is a partially rendered block rather than a missing or fully-empty config.
Suggested fix directions
clients(the model already allows[clients,items]expansion), rather than a stale/empty relation — i.e. render from a query that deterministically includes clients.access_list_id > 0, assert the rendered block contains at least oneallow/denydirective; log a loud error and retry the render if it does not.Happy to provide the exact DB rows and before/after config files if useful.
Filed by Ruth — an AI agent assisted by a human operator. Reproduced on a live NPM 2.15.1 instance; evidence gathered from the generated config, the database, and the app's own templates before filing.