Commit Graph

3 Commits (2a06b5dfb239dffd68dde785b5f4bb0ab361414c)

Author SHA1 Message Date
nexxo ba861ad579 fix(security): match ADMIN_HOSTS case-insensitively; share test helpers
DNS names are case-insensitive and Symfony's getHost() always returns lowercase,
so ADMIN_HOSTS=Admin.Example.com rejected every request and locked operators
out. Normalised in the config AND at the comparison, so the value is safe
whichever route it took in (env, cached config, runtime set).

operator()/admin() moved from RbacTest/HostManagementTest to tests/Pest.php:
they were only loaded when those files happened to be in the run, so a targeted
single-file run died on 'undefined function operator()'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 20:56:26 +02:00
nexxo f5f4d2a8cd fix(security): close the Livewire bypass of the admin host restriction
Admin actions post to /livewire/update, which a path-scoped guard skips — an
operator session could drive admin components through a PUBLIC hostname despite
ADMIN_HOSTS. The restriction is now registered as Livewire-persistent middleware
and listed on the admin route group, so Livewire re-applies it from the
component snapshot.

Proven by replaying a snapshot taken from the real rendered page: identical
payload -> 404 on a public host, 200 on the allowed host (positive control, so
the test cannot pass for the wrong reason).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 20:50:22 +02:00
nexxo 1f42c05648 feat(security): admin console can be pinned to non-public hostnames
The Proxmox fleet and the operator console must never be publicly reachable. The
primary control is the reverse proxy, but nginx here is a catch-all
(server_name _), so /admin was served on EVERY hostname — a proxy
misconfiguration would expose it. ADMIN_HOSTS pins it; any other host gets 404
(not 403: a public domain must not disclose that a console exists).

Prepended to the  group instead of the admin route group on purpose: route
middleware is reordered by Laravel's priority list, which runs  first — a
guest would then be redirected to /login and learn the console is there. Covered
by a test for exactly that case. Empty ADMIN_HOSTS = unrestricted, so nobody is
locked out by upgrading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 20:45:54 +02:00