Skip to content

Binding the core to a specific LAN IP breaks all management features with 403 "remote management disabled" (regression since v0.2.59) #229

Description

@OMSKremer

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

  1. 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.
  2. Open the Usage page, or go to OAuth and click Start for Codex/Claude.
  3. 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.rsmanagement_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.rscore_connect_host() only maps ""/0.0.0.0127.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)

  1. 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.
  2. 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.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions