diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 33638e7fc86..560be1cb896 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -23,6 +23,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- Give a webview request a loopback `RemoteAddr` (`127.0.0.1:0`) instead of the `192.0.2.1:1234` (RFC 5737 TEST-NET) placeholder. A webview request originates on the local machine, so a user `Handler` that gates on request origin now sees it as loopback rather than as a remote peer. ## Deprecated diff --git a/v3/internal/assetserver/assetserver_webview.go b/v3/internal/assetserver/assetserver_webview.go index 848b9e84bae..57a681ef157 100644 --- a/v3/internal/assetserver/assetserver_webview.go +++ b/v3/internal/assetserver/assetserver_webview.go @@ -185,8 +185,14 @@ func (a *AssetServer) processWebViewRequestInternal(r webview.Request) { req.Header = header if req.RemoteAddr == "" { - // 192.0.2.0/24 is "TEST-NET" in RFC 5737 - req.RemoteAddr = "192.0.2.1:1234" + // A webview request originates from the embedded webview on this + // machine, not from a routable peer, so give it a loopback RemoteAddr: + // a handler that gates on request origin (net.ParseIP(host).IsLoopback()) + // then sees it as the local request it is. The previous placeholder + // (192.0.2.1, RFC 5737 TEST-NET) is non-loopback — the same address the + // host checks elsewhere in this tree treat as the canonical remote peer + // — so it made the most-local surface look remote. + req.RemoteAddr = "127.0.0.1:0" } if req.RequestURI == "" && req.URL != nil { diff --git a/v3/internal/assetserver/assetserver_webview_test.go b/v3/internal/assetserver/assetserver_webview_test.go index 9ffec07ab2e..48a7460a7e8 100644 --- a/v3/internal/assetserver/assetserver_webview_test.go +++ b/v3/internal/assetserver/assetserver_webview_test.go @@ -5,6 +5,7 @@ import ( "context" "io" "log/slog" + "net" "net/http" "sync/atomic" "testing" @@ -13,6 +14,39 @@ import ( "github.com/wailsapp/wails/v3/internal/assetserver/webview" ) +// A webview request originates from the embedded webview on this machine, so a +// handler that gates on request origin must see a loopback peer. The synthesized +// RemoteAddr must therefore parse as a loopback IP. +func TestWebViewRequestPeerIsLoopback(t *testing.T) { + var peer atomic.Value // string + + srv, err := NewAssetServer(&Options{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + peer.Store(r.RemoteAddr) + w.WriteHeader(http.StatusOK) + }), + Logger: slog.Default(), + }) + if err != nil { + t.Fatalf("NewAssetServer failed: %v", err) + } + + req := &contextWebViewRequest{ctx: context.Background(), rw: &contextWebViewResponse{}} + srv.processWebViewRequest(req) + + got, _ := peer.Load().(string) + if got == "" { + t.Fatal("handler was not reached") + } + host, _, err := net.SplitHostPort(got) + if err != nil { + t.Fatalf("RemoteAddr %q is not host:port: %v", got, err) + } + if ip := net.ParseIP(host); ip == nil || !ip.IsLoopback() { + t.Fatalf("webview request RemoteAddr = %q, want a loopback peer", got) + } +} + func TestWebViewRequestCancellationReachesHandler(t *testing.T) { started := make(chan struct{}) contextErr := make(chan error, 1)