Summary
Since v0.2.59 the GUI talks to the core's management API via the configured Listen IP instead of always using 127.0.0.1. When the Listen IP is a concrete non-loopback address (e.g. 192.168.1.10), the core sees the GUI as a remote client and rejects every management call with 403 remote management disabled. Usage statistics, auth-file listing, OAuth login (Codex, Claude, …) and API-key management all stop working. Only 0.0.0.0 works for LAN access, but nothing in the UI tells the user that.
Environment
- EasyCLIProxyAPI v0.2.76 (portable build), Windows 11
- CLIProxyAPI core 7.2.152
- Worked fine up to v0.2.58
Steps to reproduce
- Settings → Network → Listen IP: enter the machine's own LAN address (e.g.
192.168.1.10), keep port 8317, save. The core restarts and listens on 192.168.1.10:8317.
- Open the Usage page, or go to OAuth and click Start for Codex/Claude.
- Every management request fails with
403 remote management disabled.
Core log (main.log):
[debug] [server.go:302] Starting API server on 192.168.1.10:8317
[warn ] [gin_logger.go:95] 403 | 192.168.1.10 | GET "/v0/management/usage-queue?count=500"
[warn ] [gin_logger.go:95] 403 | 192.168.1.10 | GET "/v0/management/auth-files"
[warn ] [gin_logger.go:95] 403 | 192.168.1.10 | GET "/v0/management/codex-auth-url"
Expected behaviour
Either the GUI keeps working with a concrete Listen IP, or the UI makes clear that LAN access must be configured via 0.0.0.0 (or automatically writes the required core setting).
Root cause
Core side (internal/api/handlers/management/handler.go, unchanged upstream behaviour):
clientIP := c.ClientIP()
localClient := clientIP == "127.0.0.1" || clientIP == "::1"
...
if !localClient && !allowRemote {
return false, http.StatusForbidden, "remote management disabled"
}
allowRemote comes from remote-management.allow-remote in config.yaml, default false.
GUI side (regression introduced in commit 2b9ceb1, "Dev (#164)", shipped in v0.2.59, together with the new free-text Listen IP field):
src-tauri/src/management_api.rs → management_endpoint() used to hard-code http://127.0.0.1:{port}/v0/management/.... It now builds the URL with core_origin(&config.host, …).
src-tauri/src/core_config/settings.rs → core_connect_host() only maps ""/0.0.0.0 → 127.0.0.1 and :: → ::1. A concrete IP is passed through unchanged.
- The GUI therefore connects to
http://192.168.1.10:8317/.... A TCP connection to your own LAN IP has that LAN IP as its source address, so the core sees clientIP == 192.168.1.10, not loopback → 403.
- The GUI never writes
remote-management.allow-remote (it only manages secret-key), so the resulting configuration is self-contradictory: the core is only reachable via the LAN IP, but refuses management from exactly that address.
Before v0.2.59 the only option was the "Allow LAN" toggle, which sets host: 0.0.0.0. The core then binds all interfaces, the GUI keeps talking to 127.0.0.1, and remote clients use the LAN IP. That still works today; only a concrete IP breaks.
Workarounds
- Use
0.0.0.0 as Listen IP instead of a specific address (recommended, no security change).
- Or, if a specific bind address is required, add manually to
cpa-core/config.yaml:
remote-management:
allow-remote: true
Note: this exposes the management API on the LAN, protected only by the secret key.
Suggested fixes (pick one)
- Document / hint in the UI: next to the Listen IP field explain that LAN access should use
0.0.0.0, and that a concrete non-loopback IP requires remote-management.allow-remote: true. Show a warning when such an IP is entered.
- Auto-configure: in
save_network_endpoint_settings (src-tauri/src/core_config/commands.rs), when the host is a concrete non-loopback, non-wildcard IP, write remote-management.allow-remote: true (and back to false when switching back to loopback), with a confirmation dialog since it changes the security posture.
- Restrict input: only offer
127.0.0.1 / 0.0.0.0 (and IPv6 equivalents) in the Listen IP field and drop support for concrete IPs.
Summary
Since v0.2.59 the GUI talks to the core's management API via the configured Listen IP instead of always using
127.0.0.1. When the Listen IP is a concrete non-loopback address (e.g.192.168.1.10), the core sees the GUI as a remote client and rejects every management call with403 remote management disabled. Usage statistics, auth-file listing, OAuth login (Codex, Claude, …) and API-key management all stop working. Only0.0.0.0works for LAN access, but nothing in the UI tells the user that.Environment
Steps to reproduce
192.168.1.10), keep port8317, save. The core restarts and listens on192.168.1.10:8317.403 remote management disabled.Core log (
main.log):Expected behaviour
Either the GUI keeps working with a concrete Listen IP, or the UI makes clear that LAN access must be configured via
0.0.0.0(or automatically writes the required core setting).Root cause
Core side (
internal/api/handlers/management/handler.go, unchanged upstream behaviour):allowRemotecomes fromremote-management.allow-remoteinconfig.yaml, defaultfalse.GUI side (regression introduced in commit
2b9ceb1, "Dev (#164)", shipped in v0.2.59, together with the new free-text Listen IP field):src-tauri/src/management_api.rs→management_endpoint()used to hard-codehttp://127.0.0.1:{port}/v0/management/.... It now builds the URL withcore_origin(&config.host, …).src-tauri/src/core_config/settings.rs→core_connect_host()only maps""/0.0.0.0→127.0.0.1and::→::1. A concrete IP is passed through unchanged.http://192.168.1.10:8317/.... A TCP connection to your own LAN IP has that LAN IP as its source address, so the core seesclientIP == 192.168.1.10, not loopback → 403.remote-management.allow-remote(it only managessecret-key), so the resulting configuration is self-contradictory: the core is only reachable via the LAN IP, but refuses management from exactly that address.Before v0.2.59 the only option was the "Allow LAN" toggle, which sets
host: 0.0.0.0. The core then binds all interfaces, the GUI keeps talking to127.0.0.1, and remote clients use the LAN IP. That still works today; only a concrete IP breaks.Workarounds
0.0.0.0as Listen IP instead of a specific address (recommended, no security change).cpa-core/config.yaml:Note: this exposes the management API on the LAN, protected only by the secret key.
Suggested fixes (pick one)
0.0.0.0, and that a concrete non-loopback IP requiresremote-management.allow-remote: true. Show a warning when such an IP is entered.save_network_endpoint_settings(src-tauri/src/core_config/commands.rs), when the host is a concrete non-loopback, non-wildcard IP, writeremote-management.allow-remote: true(and back tofalsewhen switching back to loopback), with a confirmation dialog since it changes the security posture.127.0.0.1/0.0.0.0(and IPv6 equivalents) in the Listen IP field and drop support for concrete IPs.