Security
- Check advanced_config permissions before creating a certificate on
host update (proxy, redirection and 404 hosts). A refused request
previously requested a Let's Encrypt certificate first, leaving an
orphan certificate and using up rate limit.
- Only trust X-NPM-Peer-Addr when it comes with a secret generated at
container start. The admin nginx sends both headers. Any proxy host or
stream forwarding to 127.0.0.1:3000 was also a loopback peer, so it
could forge the header and get a fresh rate-limit bucket on every
request. Without the secret (dev, or a mounted production.conf), the
header is ignored.
- Add a login limit per IP across all accounts (30 failed attempts a
minute) alongside the per-account one. Before this, one address could
spray passwords over any number of accounts. Behind a shared proxy,
everyone using that proxy shares this limit.
- Stop trusting X-Real-IP from loopback. It was only there for an older
version of the rate limiter, and it let a stream host forwarding to a
local proxy host forge the client IP and get past IP access lists.
Loopback now behaves as it does on master.
Correctness
- Refuse to delete a certificate that hosts still use (400, with counts
per host type). Their configs include its files, and a successful
revoke deleted them anyway, which broke nginx -t. Otherwise the
certificate is revoked and its files are removed even if the revoke
fails.
- Let non-admins clear a location's advanced config. Deleting the
location was already allowed and has the same effect, and a location
deleted and re-added at the same path arrives with an empty config,
which was refused.
- Clear proxy host grants (meta.proxy_host_ids) when visibility isn't
"user" or proxy hosts are hidden, and drop ids of deleted hosts.
Before, hidden grants came back when proxy hosts were shown again.
The backend enforces this; the permissions modal also stops sending
them.
- 404 host and redirection host modals now wait for the current user
to load, so an admin's advanced config change isn't dropped if they
submit before it loads.
Tests
- NonAdminHosts.cy.js: refused change doesn't create a certificate,
location rename and clear rules, grant clearing.
- Certificates.cy.js: deleting a certificate that is in use.
Security
- Redact certbot output before it reaches the API: strip the
certificate's DNS credential values, URL query strings and userinfo,
secret-looking key=value pairs and Bearer/Basic tokens. DNS plugins
can echo request URLs carrying API keys. The raw CommandError is no
longer attached as `previous`, since the error handler returns
`previous` to the client on certificate routes.
- Rate limit login/2FA on the admin nginx's real TCP peer, passed as
X-NPM-Peer-Addr ($realip_remote_addr), instead of X-Forwarded-For.
nginx trusts X-Real-IP from private ranges, so a client on the LAN or
Docker network could get a fresh bucket on every request. Behind
another proxy, limits now apply per account through that proxy.
- Bound rate limiter memory: hash keys over 128 chars and cap each store
at 50k entries, removing expired entries first and then the oldest.
Correctness
- Proxy host modal: send location advanced configs unchanged for
non-admins instead of looking them up by path, which dropped the
config from a renamed location.
- Location advanced config: locations that omit advanced_config now get
a config only after explicit ones are matched, so a new location at
an old path can't take the config from the renamed location.
- Certificate upload: check that the certificate matches the private
key (uploaded or stored) before writing, so a mismatched pair can't
break nginx -t.
- Certificate delete: keep the Let's Encrypt files when revoke fails and
hosts still use the certificate, so their configs still load.
Cleanup
- Make the rate-limit certbot hint first in the list, as its comment
says.
- Proxy host getCount uses the permissions access has already loaded
instead of querying user_permission again.
- Rate limiter: register the success refund listener in one place and
call it a fixed window, not a sliding one.
6003/6111/9103/9109/10000, plus 'Authentication error' and 'Invalid
access token') is checked before the rate-limit, DNS and HTTP hints, so
non-Cloudflare failures get a misleading Cloudflare message.
($http_host) forwards the raw client Host header, including the port and
any absolute-form URI mismatch, to every upstream and into the asset
cache key
means upload() now reads a row with no private key, so an upload that
leaves out certificate_key wipes the stored key and writeCustomCert
writes undefined to privkey.pem.
Without a password to check, a login for an unknown account skipped bcrypt
and answered far sooner than one with a wrong password, which still told
which emails have an account.
- Add in-memory sliding window rate limiter for auth token endpoints with CI/test bypass and successful request exclusion
- Add user_permission meta column migration and Objection model configuration
- Support scoping allowed proxy hosts per user when in 'Created Items Only' visibility mode
- Restrict advanced nginx configuration editing on hosts to admin users only
- Fix user permissions route ID parsing and omit id from permission update payloads
- Add allowed proxy hosts selector in permissions modal and display permissions summary badges in users table