fix: tolerate loopback Host behind reverse proxy, document Caddy setup

DSH pins privileged /api methods (settings.*, credentials.*, agentPreset.*,
host.pickDirectory, ...) to loopback hosts by design. When a reverse proxy
fronts the panel with the public Host header, those methods return 403.

The supported deployment shape is forwarding Host as loopback upstream
(header_up Host 127.0.0.1 in Caddy). This change:
- skips the login origin check when the incoming Host is loopback (proxy
  context), while still rejecting real cross-site posts on public hosts;
- documents the reverse-proxy requirement in the README;
- extends tests with raw-request coverage for non-loopback cross-site
  rejection, port tolerance, and loopback-Host skip.
This commit is contained in:
dsh
2026-08-16 02:18:07 -04:00
parent 8823049b66
commit ddb32ce129
3 changed files with 95 additions and 7 deletions
+12
View File
@@ -439,6 +439,17 @@ function sameHost(originHeader, hostHeader) {
}
}
/** True when the Host header names the loopback authority (direct access or a
* reverse proxy presenting a loopback Host upstream). */
function isLoopbackHost(hostHeader) {
try {
const hostname = new URL(`http://${hostHeader}`).hostname.toLowerCase()
return hostname === 'localhost' || hostname === '[::1]' || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)
} catch {
return false
}
}
function readBody(req, maxBytes) {
return new Promise((resolveBody) => {
let size = 0
@@ -521,6 +532,7 @@ export function installGuard(server, guard, { audit, loginPath, logoutPath, lock
if (typeof origin === 'string' &&
origin !== 'null' &&
typeof host === 'string' &&
!isLoopbackHost(host) &&
!sameHost(origin, host)
) {
audit.write({