Matches the Real-Debrid-Downloader's rd-diagnostics-mcp model so the read-only
diagnostics agent is reachable over Tailscale (or any private tunnel) the same way
the downloader is, instead of requiring an SSH local-forward.
- lib/ip-allowlist.js (NEW): fail-closed IP allowlist — normalizeIp strips
::ffff:, loopback is always allowed, an empty allowlist accepts loopback ONLY
(fail-closed), exact IP + CIDR (incl. the Tailscale CGNAT range 100.64.0.0/10) +
wildcard rules. The real socket peer IP is the authority (never a forwarded header).
- remote-server.js: rejects non-allowlisted peers at connection (close 4005). Opt-in
via config.allowlist (the existing remote-control server, which passes none, is
unaffected). Loopback always passes, so local + SSH-forward use keeps working.
- Two bind modes (config diagnostics.bindMode): "local" -> 127.0.0.1 (default),
"network" -> 0.0.0.0 but ONLY when a non-empty allowlist is set (else it stays
loopback, fail-closed). The allowlist + token gate access; the tunnel
(Tailscale/WireGuard) is the confidentiality layer (transport is still plaintext ws://).
- The connection code now carries the host: mhu1_<base64url{v,h,p,t,n,fp?,s?}>. The
gateway decode is tolerant of the legacy {port,token,label} keys; connect_server
takes the host from the code (host arg is an optional override). Proven end-to-end:
the integration harness now connects with NO host arg and resolves it from the code.
- Renderer: Sichtbarkeit selector (local/network), public-host input with
suggested-host chips (os.networkInterfaces — the Tailscale IP shows up there),
allowlist textarea (network mode), and network-requires-allowlist validation.
- main.js: bindMode->host, getSuggestedRemoteHosts, host-in-code, allowlist plumbed
into startDiagnosticAgent + the diagnostics IPC (get/save/status).
- docs: rewritten for Tailscale (set the allowlist to your tailnet, put the Tailscale
IP/MagicDNS in the code address — no SSH forward needed).
This supersedes the v3.3.85 hard loopback-lock with the downloader's allowlist model.
Tests: lib/ip-allowlist (8) + remote-server allowlist wiring/loopback (2) + gateway
decode (host short-key + legacy tolerance). 393 app tests + 9 gateway tests + e2e +
host-in-code integration + adversarial all green; lint 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Repo-side hardening and tooling from the intensive test round (none of this ships
in the app installer).
- gateway: registry.json (holds bearer tokens) now gets a best-effort owner-only
NTFS ACL on Windows via `icacls /inheritance:r /grant:r <user>:F` (the chmod
0600 is a no-op on NTFS); verified the file ends up <user>:(F) only.
- gateway: connect_server now reads the app version from the real get_system_info
shape (data.app.version / data.agent.version), so "connected to vX.Y.Z" works.
- gateway: read_log tool description documents grep as a case-insensitive substring
filter with "|" alternation (not a regex), matching the agent-side change.
- gateway: standalone verification harnesses moved to gateway/verify/ (so
`node --test` only sweeps real unit tests) and exposed via `npm run verify`:
e2e-verify, integration-mcp (live gateway-MCP <-> agent, all 14 tools), and
adversarial-probe (redaction fuzz + ReDoS + lockout). `npm test` runs the units.
- eslint: gateway/** now lints as ESM (sourceType module) via a dedicated block;
global ignores fixed so `eslint .` is clean across the whole project (0 errors).
- docs/remote-diagnostics-setup.md: made the transport story honest — the agent
speaks plaintext ws:// over enforced loopback; the SSH/WireGuard tunnel is the
ONLY confidentiality layer (wss/TLS + cert-pin is a documented future mode, not
active). Removed the stale "bind to a LAN/VPN IP" guidance (loopback is enforced).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>